<commit_before>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "ListHookedConsumer.hpp"
#include "ListHookedKeyboard.hpp"
#include "ListHookedPointing.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"
#include "VK_IOHIDPOSTEVENT.hpp"

namespace org_pqrs_KeyRemap4MacBook {
  List* EventOutputQueue::queue_ = NULL;
  TimerWrapper EventOutputQueue::fire_timer_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new List();
    fire_timer_.initialize(&workloop, NULL, EventOutputQueue::fire_timer_callback);
  }

  void
  EventOutputQueue::terminate(void)
  {
    fire_timer_.terminate();

    if (queue_) {
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
#define PUSH_TO_OUTPUTQUEUE {               \
    if (! queue_) return;                   \
                                            \
    queue_->push_back(new Item(p));         \
    fire_timer_.setTimeoutMS(DELAY, false); \
}
  void EventOutputQueue::push(const Params_KeyboardEventCallBack& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.eventType == EventType::DOWN) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.ex_iskeydown) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_RelativePointerEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.buttons != Buttons(0)) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_ScrollWheelEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    FlagStatus::globalFlagStatus().sticky_clear();
  }
  void EventOutputQueue::push(const Params_Wait& p) {
    PUSH_TO_OUTPUTQUEUE;
  }
#undef PUSH_TO_OUTPUTQUEUE

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::fire_timer_callback(OSObject* /* owner */, IOTimerEventSource* /* sender */)
  {
    if (! queue_) return;

    // IOLOG_DEVEL("EventOutputQueue::fire queue_->size = %d\n", static_cast<int>(queue_->size()));

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    int delay = DELAY;

    // ----------------------------------------
    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
      {
        Params_KeyboardEventCallBack* params = (p->params).get_Params_KeyboardEventCallBack();
        if (params) {
          bool handled = VirtualKey::handleAfterEnqueued(*params);
          if (! handled) {
            ListHookedKeyboard::instance().apply(*params);
          }
          delay = Config::get_wait_between_sequential_keys();
        }
        break;
      }
      case ParamsUnion::KEYBOARD_SPECIAL:
      {
        Params_KeyboardSpecialEventCallback* params = (p->params).get_Params_KeyboardSpecialEventCallback();
        if (params) {
          if (! ListHookedConsumer::instance().apply(*params)) {
            // If there is no consumer device, we send an event as a software key.
            VirtualKey::VK_IOHIDPOSTEVENT::post(*params);
          }
        }
        break;
      }
      case ParamsUnion::RELATIVE_POINTER:
      {
        Params_RelativePointerEventCallback* params = (p->params).get_Params_RelativePointerEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::SCROLL_WHEEL:
      {
        Params_ScrollWheelEventCallback* params = (p->params).get_Params_ScrollWheelEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::WAIT:
      {
        Params_Wait* params = (p->params).get_Params_Wait();
        if (params) {
          delay = params->milliseconds;
        }
        break;
      }
      case ParamsUnion::UPDATE_FLAGS:
        // do nothing
        break;
    }

    queue_->pop_front();

    // ----------------------------------------
    if (! queue_->empty()) {
      fire_timer_.setTimeoutMS(delay);
    }
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    if (lastFlags_ == toFlags) return;

    // ------------------------------------------------------------
    // At first we handle KeyUp events and handle KeyDown events next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // KeyUp
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    lastFlags_ = toFlags;
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    if (VirtualKey::handle(params)) return;

    // ----------------------------------------
    if (params.key == KeyCode::VK_MODIFIER_EXTRA1 ||
        params.key == KeyCode::VK_MODIFIER_EXTRA2 ||
        params.key == KeyCode::VK_MODIFIER_EXTRA3 ||
        params.key == KeyCode::VK_MODIFIER_EXTRA4 ||
        params.key == KeyCode::VK_MODIFIER_EXTRA5) return;

    // ------------------------------------------------------------
    KeyCode newkeycode = params.key;
    Flags newflags = params.flags;

    KeyCode::reverseNormalizeKey(newkeycode, newflags, params.eventType, params.keyboardType);

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (newkeycode == KeyCode::VK_NONE ||
        newkeycode == KeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire(newflags, params.keyboardType);

    if (params.eventType == EventType::DOWN || params.eventType == EventType::UP) {
      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params.eventType, newflags, newkeycode,
                                                                                params.charCode, params.charSet, params.origCharCode, params.origCharSet,
                                                                                params.keyboardType, params.repeat));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }

      if (! params.repeat) {
        if (params.eventType == EventType::DOWN) {
          PressDownKeys::add(newkeycode, params.keyboardType);
        } else {
          PressDownKeys::remove(newkeycode, params.keyboardType);
        }
      }
    }
  }

  void
  EventOutputQueue::FireKey::fire_downup(Flags flags, KeyCode key, KeyboardType keyboardType)
  {
    ModifierFlag f = key.getModifierFlag();

    if (f != ModifierFlag::ZERO) {
      FlagStatus::ScopedTemporaryFlagsChanger stfc(FlagStatus::globalFlagStatus(), flags);

      // We operate FlagStatus for the case "key == KeyCode::CAPSLOCK".
      FlagStatus::globalFlagStatus().increase(f);
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY,
                                                                                FlagStatus::globalFlagStatus().makeFlags(),
                                                                                key,
                                                                                keyboardType,
                                                                                false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }

      FlagStatus::globalFlagStatus().decrease(f);
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY,
                                                                                FlagStatus::globalFlagStatus().makeFlags(),
                                                                                key,
                                                                                keyboardType,
                                                                                false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }

    } else {
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::DOWN, flags, key, keyboardType, false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::UP, flags, key, keyboardType, false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (params.key == ConsumerKeyCode::VK_NONE ||
        params.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params.eventType, params.flags, params.key, params.repeat));
    if (ptr) {
      EventOutputQueue::push(*ptr);
    }
  }

  void
  EventOutputQueue::FireConsumer::fire_downup(Flags flags, ConsumerKeyCode key)
  {
    {
      Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(EventType::DOWN,
                                                                                flags,
                                                                                key,
                                                                                false));
      if (! ptr) return;
      FireConsumer::fire(*ptr);
    }
    {
      Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(EventType::UP,
                                                                                flags,
                                                                                key,
                                                                                false));
      if (! ptr) return;
      FireConsumer::fire(*ptr);
    }
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // When changing space to command+left click,
    //   __KeyToKey__ KeyCode::SPACE, PointingButton::LEFT, ModifierFlag::COMMAND_L
    //
    // We need to release command key after left click was released as follows.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) MouseUp   Left
    //   (4) KeyUp     Command_L
    //
    // For mouse drag support, command modifier is increased as normal (not temporary_increase).
    // Therefore, command modifier is decreased when space key is released.
    // If we call FireModifiers::fire() at the begining of this function,
    // input events are fired as follows order.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) KeyUp     Command_L
    //   (4) MouseUp   Left
    //
    // It's not desired order.
    // So, we call FireModifiers::fire() at the end of this function when button is released.

    Buttons releasedButtons = lastButtons_;
    releasedButtons.remove(toButtons);
    bool isButtonReleased = ! releasedButtons.isNONE();

    if (! isButtonReleased) {
      FireModifiers::fire();
    }

    // Sending button event
    if (lastButtons_ != toButtons) {
      lastButtons_ = toButtons;

      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, 0, 0, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    // Sending cursor
    if (dx != 0 || dy != 0) {
      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    if (isButtonReleased) {
      FireModifiers::fire();
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  void
  EventOutputQueue::FireScrollWheel::fire(int delta1, int delta2)
  {
    short deltaAxis1;
    short deltaAxis2;
    IOFixed fixedDelta1;
    IOFixed fixedDelta2;
    SInt32 pointDelta1;
    SInt32 pointDelta2;

    int relative2scroll_rate = Config::get_essential_config(BRIDGE_ESSENTIAL_CONFIG_INDEX_pointing_relative2scroll_rate);

    deltaAxis1 = (delta1 * relative2scroll_rate) / 1024 / DELTA_SCALE;
    deltaAxis2 = (delta2 * relative2scroll_rate) / 1024 / DELTA_SCALE;

    fixedDelta1 = (delta1 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;
    fixedDelta2 = (delta2 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;

    pointDelta1 = (delta1 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;
    pointDelta2 = (delta2 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;

    // see IOHIDSystem/IOHIDevicePrivateKeys.h about options.
    const int kScrollTypeContinuous_ = 0x0001;
    int options = kScrollTypeContinuous_;

    Params_ScrollWheelEventCallback::auto_ptr ptr(Params_ScrollWheelEventCallback::alloc(deltaAxis1,  deltaAxis2, 0,
                                                                                fixedDelta1, fixedDelta2, 0,
                                                                                pointDelta1, pointDelta2, 0,
                                                                                options));
    if (! ptr) return;
    EventOutputQueue::FireScrollWheel::fire(*ptr);
  }

  // ======================================================================
  void
  EventOutputQueue::FireWait::fire(const Params_Wait& params)
  {
    EventOutputQueue::push(params);
  }
}
<commit_msg>remvoe VK_MODIFIER_EXTRA1<commit_after>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "ListHookedConsumer.hpp"
#include "ListHookedKeyboard.hpp"
#include "ListHookedPointing.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"
#include "VK_IOHIDPOSTEVENT.hpp"

namespace org_pqrs_KeyRemap4MacBook {
  List* EventOutputQueue::queue_ = NULL;
  TimerWrapper EventOutputQueue::fire_timer_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new List();
    fire_timer_.initialize(&workloop, NULL, EventOutputQueue::fire_timer_callback);
  }

  void
  EventOutputQueue::terminate(void)
  {
    fire_timer_.terminate();

    if (queue_) {
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
#define PUSH_TO_OUTPUTQUEUE {               \
    if (! queue_) return;                   \
                                            \
    queue_->push_back(new Item(p));         \
    fire_timer_.setTimeoutMS(DELAY, false); \
}
  void EventOutputQueue::push(const Params_KeyboardEventCallBack& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.eventType == EventType::DOWN) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.ex_iskeydown) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_RelativePointerEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    if (p.buttons != Buttons(0)) {
      FlagStatus::globalFlagStatus().sticky_clear();
    }
  }
  void EventOutputQueue::push(const Params_ScrollWheelEventCallback& p) {
    PUSH_TO_OUTPUTQUEUE;
    FlagStatus::globalFlagStatus().sticky_clear();
  }
  void EventOutputQueue::push(const Params_Wait& p) {
    PUSH_TO_OUTPUTQUEUE;
  }
#undef PUSH_TO_OUTPUTQUEUE

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::fire_timer_callback(OSObject* /* owner */, IOTimerEventSource* /* sender */)
  {
    if (! queue_) return;

    // IOLOG_DEVEL("EventOutputQueue::fire queue_->size = %d\n", static_cast<int>(queue_->size()));

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    int delay = DELAY;

    // ----------------------------------------
    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
      {
        Params_KeyboardEventCallBack* params = (p->params).get_Params_KeyboardEventCallBack();
        if (params) {
          bool handled = VirtualKey::handleAfterEnqueued(*params);
          if (! handled) {
            ListHookedKeyboard::instance().apply(*params);
          }
          delay = Config::get_wait_between_sequential_keys();
        }
        break;
      }
      case ParamsUnion::KEYBOARD_SPECIAL:
      {
        Params_KeyboardSpecialEventCallback* params = (p->params).get_Params_KeyboardSpecialEventCallback();
        if (params) {
          if (! ListHookedConsumer::instance().apply(*params)) {
            // If there is no consumer device, we send an event as a software key.
            VirtualKey::VK_IOHIDPOSTEVENT::post(*params);
          }
        }
        break;
      }
      case ParamsUnion::RELATIVE_POINTER:
      {
        Params_RelativePointerEventCallback* params = (p->params).get_Params_RelativePointerEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::SCROLL_WHEEL:
      {
        Params_ScrollWheelEventCallback* params = (p->params).get_Params_ScrollWheelEventCallback();
        if (params) {
          ListHookedPointing::instance().apply(*params);
        }
        break;
      }
      case ParamsUnion::WAIT:
      {
        Params_Wait* params = (p->params).get_Params_Wait();
        if (params) {
          delay = params->milliseconds;
        }
        break;
      }
      case ParamsUnion::UPDATE_FLAGS:
        // do nothing
        break;
    }

    queue_->pop_front();

    // ----------------------------------------
    if (! queue_->empty()) {
      fire_timer_.setTimeoutMS(delay);
    }
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    if (lastFlags_ == toFlags) return;

    // ------------------------------------------------------------
    // At first we handle KeyUp events and handle KeyDown events next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // KeyUp
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (size_t i = 0; i < FlagStatus::globalFlagStatus().itemSize(); ++i) {
      ModifierFlag flag = FlagStatus::globalFlagStatus().getFlag(i);

      // Skipping ModifierFlag::NUMPAD
      if (flag.getKeyCode() == KeyCode::VK_NONE) continue;
      // Skipping virtual modifiers.
      if (flag.getRawBits() == 0) continue;

      // ----------------------------------------
      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    lastFlags_ = toFlags;
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    if (VirtualKey::handle(params)) return;
    if (params.eventType == EventType::MODIFY) return;

    // ------------------------------------------------------------
    KeyCode newkeycode = params.key;
    Flags newflags = params.flags;

    KeyCode::reverseNormalizeKey(newkeycode, newflags, params.eventType, params.keyboardType);

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (newkeycode == KeyCode::VK_NONE ||
        newkeycode == KeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire(newflags, params.keyboardType);

    if (params.eventType == EventType::DOWN || params.eventType == EventType::UP) {
      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params.eventType, newflags, newkeycode,
                                                                                params.charCode, params.charSet, params.origCharCode, params.origCharSet,
                                                                                params.keyboardType, params.repeat));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }

      if (! params.repeat) {
        if (params.eventType == EventType::DOWN) {
          PressDownKeys::add(newkeycode, params.keyboardType);
        } else {
          PressDownKeys::remove(newkeycode, params.keyboardType);
        }
      }
    }
  }

  void
  EventOutputQueue::FireKey::fire_downup(Flags flags, KeyCode key, KeyboardType keyboardType)
  {
    ModifierFlag f = key.getModifierFlag();

    if (f != ModifierFlag::ZERO) {
      FlagStatus::ScopedTemporaryFlagsChanger stfc(FlagStatus::globalFlagStatus(), flags);

      // We operate FlagStatus for the case "key == KeyCode::CAPSLOCK".
      FlagStatus::globalFlagStatus().increase(f);
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY,
                                                                                FlagStatus::globalFlagStatus().makeFlags(),
                                                                                key,
                                                                                keyboardType,
                                                                                false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }

      FlagStatus::globalFlagStatus().decrease(f);
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY,
                                                                                FlagStatus::globalFlagStatus().makeFlags(),
                                                                                key,
                                                                                keyboardType,
                                                                                false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }

    } else {
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::DOWN, flags, key, keyboardType, false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }
      {
        Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::UP, flags, key, keyboardType, false));
        if (! ptr) return;
        FireKey::fire(*ptr);
      }
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (params.key == ConsumerKeyCode::VK_NONE ||
        params.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params.eventType, params.flags, params.key, params.repeat));
    if (ptr) {
      EventOutputQueue::push(*ptr);
    }
  }

  void
  EventOutputQueue::FireConsumer::fire_downup(Flags flags, ConsumerKeyCode key)
  {
    {
      Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(EventType::DOWN,
                                                                                flags,
                                                                                key,
                                                                                false));
      if (! ptr) return;
      FireConsumer::fire(*ptr);
    }
    {
      Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(EventType::UP,
                                                                                flags,
                                                                                key,
                                                                                false));
      if (! ptr) return;
      FireConsumer::fire(*ptr);
    }
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // When changing space to command+left click,
    //   __KeyToKey__ KeyCode::SPACE, PointingButton::LEFT, ModifierFlag::COMMAND_L
    //
    // We need to release command key after left click was released as follows.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) MouseUp   Left
    //   (4) KeyUp     Command_L
    //
    // For mouse drag support, command modifier is increased as normal (not temporary_increase).
    // Therefore, command modifier is decreased when space key is released.
    // If we call FireModifiers::fire() at the begining of this function,
    // input events are fired as follows order.
    //   (1) KeyDown   Command_L
    //   (2) MouseDown Left
    //   (3) KeyUp     Command_L
    //   (4) MouseUp   Left
    //
    // It's not desired order.
    // So, we call FireModifiers::fire() at the end of this function when button is released.

    Buttons releasedButtons = lastButtons_;
    releasedButtons.remove(toButtons);
    bool isButtonReleased = ! releasedButtons.isNONE();

    if (! isButtonReleased) {
      FireModifiers::fire();
    }

    // Sending button event
    if (lastButtons_ != toButtons) {
      lastButtons_ = toButtons;

      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, 0, 0, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    // Sending cursor
    if (dx != 0 || dy != 0) {
      Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy, PointingButton::NONE, false));
      if (! ptr) return;
      Params_RelativePointerEventCallback& params = *ptr;

      EventOutputQueue::push(params);
    }

    if (isButtonReleased) {
      FireModifiers::fire();
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  void
  EventOutputQueue::FireScrollWheel::fire(int delta1, int delta2)
  {
    short deltaAxis1;
    short deltaAxis2;
    IOFixed fixedDelta1;
    IOFixed fixedDelta2;
    SInt32 pointDelta1;
    SInt32 pointDelta2;

    int relative2scroll_rate = Config::get_essential_config(BRIDGE_ESSENTIAL_CONFIG_INDEX_pointing_relative2scroll_rate);

    deltaAxis1 = (delta1 * relative2scroll_rate) / 1024 / DELTA_SCALE;
    deltaAxis2 = (delta2 * relative2scroll_rate) / 1024 / DELTA_SCALE;

    fixedDelta1 = (delta1 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;
    fixedDelta2 = (delta2 * relative2scroll_rate) * (POINTING_FIXED_SCALE / 1024) / DELTA_SCALE;

    pointDelta1 = (delta1 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;
    pointDelta2 = (delta2 * POINTING_POINT_SCALE * relative2scroll_rate) / 1024 / DELTA_SCALE;

    // see IOHIDSystem/IOHIDevicePrivateKeys.h about options.
    const int kScrollTypeContinuous_ = 0x0001;
    int options = kScrollTypeContinuous_;

    Params_ScrollWheelEventCallback::auto_ptr ptr(Params_ScrollWheelEventCallback::alloc(deltaAxis1,  deltaAxis2, 0,
                                                                                fixedDelta1, fixedDelta2, 0,
                                                                                pointDelta1, pointDelta2, 0,
                                                                                options));
    if (! ptr) return;
    EventOutputQueue::FireScrollWheel::fire(*ptr);
  }

  // ======================================================================
  void
  EventOutputQueue::FireWait::fire(const Params_Wait& params)
  {
    EventOutputQueue::push(params);
  }
}
<|endoftext|>


<commit_before>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//
// npapitest
//
// This is a NPAPI Plugin Program which is used to test the Browser's NPAPI
// host implementation.  It is used in conjunction with the npapi_unittest.
//
// As a NPAPI Plugin, you can invoke it by creating a web page of the following
// type:
//
//     <embed src="content-to-load" type="application/vnd.npapi-test"
//      name="test-name">
//
// arguments:
//     src:      This is the initial content which will be sent to the plugin.
//     type:     Must be "application/vnd.npapi-test"
//     name:     The testcase to run when invoked
//     id:       The id of the test being run (for testing concurrent plugins)
//
// The Plugin drives the actual test, calling host functions and validating the
// Host callbacks which it receives.  It is the duty of the plugin to record
// all errors.
//
// To indicate test completion, the plugin expects the containing HTML page to
// implement two javascript functions:
//     onSuccess(string testname);
//     onFailure(string testname, string results);
// The HTML host pages used in this test will then set a document cookie
// which the automated test framework can poll for and discover that the
// test has completed.
//
//
// TESTS
// When the PluginClient receives a NPP_New callback from the browser,
// it looks at the "name" argument which is passed in.  It verifies that
// the name matches a known test, and instantiates that test.  The test is
// a subclass of PluginTest.
//
//

#include "base/basictypes.h"

#if defined(OS_WIN)
#include <windows.h>
#endif

#if defined(__GNUC__) && __GNUC__ >= 4
#define EXPORT __attribute__((visibility ("default")))
#else
#define EXPORT
#endif

#include "webkit/glue/plugins/test/plugin_client.h"

#if defined(OS_WIN)
BOOL API_CALL DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
  return TRUE;
}
#endif

extern "C" {
EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) {
  return NPAPIClient::PluginClient::GetEntryPoints(pFuncs);
}

EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* pFuncs) {
  return NPAPIClient::PluginClient::Initialize(pFuncs);
}

EXPORT NPError API_CALL NP_Shutdown() {
  return NPAPIClient::PluginClient::Shutdown();
}

#if defined(OS_POSIX) && !defined(OS_MACOSX)
EXPORT const char* API_CALL NP_GetMIMEDescription(void) {
  // The layout test LayoutTests/fast/js/navigator-mimeTypes-length.html
  // asserts that the number of mimetypes handled by plugins should be
  // greater than the number of plugins.  We specify a mimetype here so
  // this plugin has at least one.
  return "application/x-npapi-test-netscape:npapitestnetscape:"
         "npapi test netscape content";
}
#endif
} // extern "C"

namespace WebCore {
  const char* currentTextBreakLocaleID() { return "en_us"; }
}
<commit_msg>Added linux-specific NP_Initialize function so that the plugin is properly initialized. Also fixed the mime-type to be consistent with that on windows and mac. Review URL: http://codereview.chromium.org/3129005<commit_after>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//
// npapitest
//
// This is a NPAPI Plugin Program which is used to test the Browser's NPAPI
// host implementation.  It is used in conjunction with the npapi_unittest.
//
// As a NPAPI Plugin, you can invoke it by creating a web page of the following
// type:
//
//     <embed src="content-to-load" type="application/vnd.npapi-test"
//      name="test-name">
//
// arguments:
//     src:      This is the initial content which will be sent to the plugin.
//     type:     Must be "application/vnd.npapi-test"
//     name:     The testcase to run when invoked
//     id:       The id of the test being run (for testing concurrent plugins)
//
// The Plugin drives the actual test, calling host functions and validating the
// Host callbacks which it receives.  It is the duty of the plugin to record
// all errors.
//
// To indicate test completion, the plugin expects the containing HTML page to
// implement two javascript functions:
//     onSuccess(string testname);
//     onFailure(string testname, string results);
// The HTML host pages used in this test will then set a document cookie
// which the automated test framework can poll for and discover that the
// test has completed.
//
//
// TESTS
// When the PluginClient receives a NPP_New callback from the browser,
// it looks at the "name" argument which is passed in.  It verifies that
// the name matches a known test, and instantiates that test.  The test is
// a subclass of PluginTest.
//
//

#include "base/basictypes.h"

#if defined(OS_WIN)
#include <windows.h>
#endif

#if defined(__GNUC__) && __GNUC__ >= 4
#define EXPORT __attribute__((visibility ("default")))
#else
#define EXPORT
#endif

#include "webkit/glue/plugins/test/plugin_client.h"

#if defined(OS_WIN)
BOOL API_CALL DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
  return TRUE;
}
#endif

extern "C" {
EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) {
  return NPAPIClient::PluginClient::GetEntryPoints(pFuncs);
}

EXPORT NPError API_CALL NP_Shutdown() {
  return NPAPIClient::PluginClient::Shutdown();
}

#if defined(OS_WIN) || defined(OS_MACOSX)
EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnFuncs) {
  return NPAPIClient::PluginClient::Initialize(npnFuncs);
}
#elif defined(OS_POSIX)
EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnFuncs,
                                      NPPluginFuncs* nppFuncs) {
  NPError error = NPAPIClient::PluginClient::Initialize(npnFuncs);
  if (error == NPERR_NO_ERROR) {
    error = NP_GetEntryPoints(nppFuncs);
  }
  return error;
}

EXPORT NPError API_CALL NP_GetValue(NPP instance, NPPVariable variable,
                                    void* value) {
  NPError err = NPERR_NO_ERROR;

  switch (variable) {
    case NPPVpluginNameString:
      *(static_cast<const char**>(value)) = "NPAPI Test Plugin";
      break;
    case NPPVpluginDescriptionString:
      *(static_cast<const char**>(value)) =
          "Simple NPAPI plug-in for Chromium unit tests";
      break;
    case NPPVpluginNeedsXEmbed:
      *(static_cast<NPBool*>(value)) = true;
      break;
    default:
      err = NPERR_GENERIC_ERROR;
      break;
  }

  return err;
}

EXPORT const char* API_CALL NP_GetMIMEDescription(void) {
  // The layout test LayoutTests/fast/js/navigator-mimeTypes-length.html
  // asserts that the number of mimetypes handled by plugins should be
  // greater than the number of plugins.  We specify a mimetype here so
  // this plugin has at least one.
  return "application/vnd.npapi-test:npapitest:test npapi";
}
#endif  // OS_POSIX
} // extern "C"

namespace WebCore {
  const char* currentTextBreakLocaleID() { return "en_us"; }
}
<|endoftext|>


<commit_before>#pragma once

#include <vpp/core/pixel_wise.hh>

namespace vpp
{
  template <typename OPTS, typename... Params>
  class block_wise_runner
  {
  public:
    typedef block_wise_runner<OPTS, Params...> self;

    block_wise_runner(vint2 block_size, std::tuple<Params...> t, OPTS options = iod::D())
      : block_size_(block_size), ranges_(t), options_(options) {}

    template <typename ...A>
    auto operator()(A... _options)
    {
      auto new_options = iod::D(_options...);
      return block_wise_runner<decltype(new_options), Params...>
        (block_size_, ranges_, new_options);
    }

    template <typename F>
    void operator|(F fun)
    {
      auto p1 = std::get<0>(ranges_).first_point_coordinates();
      auto p2 = std::get<0>(ranges_).last_point_coordinates();

      int rstart = p1[0];
      int rend = p2[0];

      int cstart = p1[1];
      int cend = p2[1];

      int nr = std::ceil((1 + rend - rstart) / float(block_size_[0]));
      int nc = std::ceil((1 + cend - cstart) / float(block_size_[1]));

      box2d grid(vint2(0, 0),
                 vint2(nr - 1, nc - 1));

      pixel_wise(grid)(options_) | [&] (vint2 block)
      {
        int r1 = rstart + block[0] * block_size_[0];
        int r2 = std::min(rstart + (block[0] + 1) * block_size_[0] - 1, rend);

        int c1 = cstart + block[1] * block_size_[1];
        int c2 = std::min(cstart + (block[1] + 1) * block_size_[1] - 1, cend);

        box2d b(vint2{std::min(r1, r2), std::min(c1, c2)},
                vint2{std::max(r1, r2), std::max(c1, c2)});

        internals::apply_args_transform(this->ranges_, fun, [&b] (auto& i) { return i | b; });
      };
    }

  private:
    vint2 block_size_;
    std::tuple<Params...> ranges_;
    OPTS options_;
  };

  template <typename... PS>
  auto block_wise(vint2 block_size, PS&&... params)
  {
    return block_wise_runner<iod::sio<>, PS...>(block_size, std::forward_as_tuple(params...));
  }

}
<commit_msg>re-Add row_wise.<commit_after>#pragma once

#include <vpp/core/pixel_wise.hh>

namespace vpp
{
  template <typename OPTS, typename... Params>
  class block_wise_runner
  {
  public:
    typedef block_wise_runner<OPTS, Params...> self;

    block_wise_runner(vint2 block_size, std::tuple<Params...> t, OPTS options = iod::D())
      : block_size_(block_size), ranges_(t), options_(options) {}

    template <typename ...A>
    auto operator()(A... _options)
    {
      auto new_options = iod::D(_options...);
      return block_wise_runner<decltype(new_options), Params...>
        (block_size_, ranges_, new_options);
    }

    template <typename F>
    void operator|(F fun)
    {
      auto p1 = std::get<0>(ranges_).first_point_coordinates();
      auto p2 = std::get<0>(ranges_).last_point_coordinates();

      int rstart = p1[0];
      int rend = p2[0];

      int cstart = p1[1];
      int cend = p2[1];

      int nr = std::ceil((1 + rend - rstart) / float(block_size_[0]));
      int nc = std::ceil((1 + cend - cstart) / float(block_size_[1]));

      box2d grid(vint2(0, 0),
                 vint2(nr - 1, nc - 1));

      pixel_wise(grid)(options_) | [&] (vint2 block)
      {
        int r1 = rstart + block[0] * block_size_[0];
        int r2 = std::min(rstart + (block[0] + 1) * block_size_[0] - 1, rend);

        int c1 = cstart + block[1] * block_size_[1];
        int c2 = std::min(cstart + (block[1] + 1) * block_size_[1] - 1, cend);

        box2d b(vint2{std::min(r1, r2), std::min(c1, c2)},
                vint2{std::max(r1, r2), std::max(c1, c2)});

        internals::apply_args_transform(this->ranges_, fun, [&b] (auto& i) { return i | b; });
      };
    }

  private:
    vint2 block_size_;
    std::tuple<Params...> ranges_;
    OPTS options_;
  };

  template <typename... PS>
  auto block_wise(vint2 block_size, PS&&... params)
  {
    return block_wise_runner<iod::sio<>, PS...>(block_size, std::forward_as_tuple(params...));
  }

  template <typename P0, typename... PS>
  auto row_wise(P0&& a0, PS&&... params)
  {
    auto p1 = a0.first_point_coordinates();
    auto p2 = a0.last_point_coordinates();

    return block_wise(vint2(1, 1 + p2[1] - p1[1]),
                      std::forward<P0>(a0), std::forward<PS>(params)...);
  }

}
<|endoftext|>


<commit_before>#pragma once

#include <iostream>
#include <tuple>
#include <iod/iod.hh>
#include <iod/callable_traits.hh>
#include <iod/symbol.hh>

#include <vpp/core/imageNd.hh>
#include <vpp/core/image2d.hh>
#include <vpp/core/boxNd.hh>
#include <vpp/core/tuple_utils.hh>

#include <vpp/core/symbols.hh>
#include <vpp/core/block_wise.hh>

namespace vpp
{
  // Backends
  struct openmp {};
  struct pthreads {}; // Todo
  struct cpp_amp {}; // Todo

  template <typename T>
  struct get_row_iterator
  {
    typedef typename T::row_iterator type;
  };

  template <typename T>
  struct get_row_iterator<const T>
  {
    typedef typename T::const_row_iterator type;
  };

  template <typename T>
  using get_row_iterator_t = typename get_row_iterator<std::remove_reference_t<T>>::type;

  template <typename B, typename... Params>
  class parallel_for_pixel_wise_runner;

  using s::row_forward;
  using s::row_backward;
  using s::col_forward;
  using s::col_backward;
  using s::mem_forward;
  using s::mem_backward;
  using s::serial;
  using s::block_size;
  using s::no_threads;

  // Pixel wise takes a variable number of 2d ranges. (Todo: implement Nd version).
  // A range should respect this interface:

  //   vint2 first_point_coordinates() -> the first point coordinates of the range.
  //   vint2 last_point_coordinates() -> the last point of the range.
  //   typedefs row_iterator, const_row_iterator -> a class to iterate on each row of the range.
  //       row_iterator(vint2 p, Range range) -> build an iterator an place it at position p.
  //       operator!=(row_iterator a, row_iterator b) -> return true if a != b.
  //       operator* -> return the current object (pixel, neighborhood access, point coordinates, ...)

  template <typename OPTS, typename... Params>
  class parallel_for_pixel_wise_runner<openmp, OPTS, Params...>
  {
  public:
    typedef parallel_for_pixel_wise_runner<openmp, OPTS, Params...> self;

    parallel_for_pixel_wise_runner(std::tuple<Params...> t, OPTS opts = iod::D())
      : ranges_(t), options_(opts) {}

    template <typename F>
    void run_row_first(F fun);

    template <typename F>
    void run_col_first_parallel(F fun);

    template <typename F>
    void run(F fun, bool parallel)
    {
      if (!options_.has(col_backward) and !options_.has(col_forward))
        run_row_first(fun);
      else
        if (parallel and !options_.has(row_backward) and !options_.has(row_forward))
          run_col_first_parallel(fun);
        else
          run_row_first(fun);
    }

    template <typename ...A>
    auto operator()(A... _options)
    {
      auto new_options = iod::D(_options...);
      return parallel_for_pixel_wise_runner<openmp, decltype(new_options), Params...>
        (ranges_, new_options);
    }

    template <typename ...A>
    auto operator()(iod::iod_object<A...> _options)
    {
      auto new_options = _options;
      return parallel_for_pixel_wise_runner<openmp, decltype(new_options), Params...>
        (ranges_, new_options);
    }

    // Compute the return type of a given kernel function.
    template <typename F>
    using kernel_return_type = decltype(std::declval<F>()(*std::declval<get_row_iterator_t<Params>>()...));

    // if fun -> void.
    template <typename F,
              typename X = std::enable_if_t<std::is_same<kernel_return_type<F>, void>::value>>
    void operator|(F fun)
    {
      run(fun, true);
    }

    // if fun -> something != void.
    template <typename F,
              typename X = std::enable_if_t<!std::is_same<kernel_return_type<F>, void>::value>>
    auto operator|(F fun)
    {
      auto p1 = std::get<0>(ranges_).first_point_coordinates();
      auto p2 = std::get<0>(ranges_).last_point_coordinates();

      typedef kernel_return_type<F> fun_return_type;
      image2d<fun_return_type> out(box2d(p1, p2));
      auto ranges = std::tuple_cat(std::make_tuple(out), ranges_);
      pixel_wise(ranges)(options_) |
        [&fun] (auto& o,
                decltype(*std::declval<get_row_iterator_t<Params>>())... ps)
      { o = fun(ps...); };

      return out;
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    OPTS options_;
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  auto pixel_wise(PS&&... params)
  {
    return parallel_for_pixel_wise_runner<openmp, iod::iod_object<>, PS...>(std::forward_as_tuple(params...));
  }

  template <typename... PS>
  auto pixel_wise(std::tuple<PS...>& params)
  {
    return parallel_for_pixel_wise_runner<openmp, iod::iod_object<>, PS...>(params);
  }

  template <typename B, typename... Params>
  class parallel_for_row_wise_runner;

  template <typename... Params>
  class parallel_for_row_wise_runner<openmp, Params...>
  {
  public:

    parallel_for_row_wise_runner(std::tuple<Params...> t) : ranges_(t) {}

    template <typename F>
    void run(F fun, bool parallel)
    {
      auto p1 = get_p1(std::get<0>(ranges_));
      auto p2 = get_p2(std::get<0>(ranges_));

      int start = p1[0];
      int end = p2[0];
#pragma omp parallel for num_threads (parallel ? omp_get_num_procs() : 0)
      for (int r = start; r <= end; r++)
      {
        auto cur_ = internals::tuple_transform(ranges_, [&] (auto& range) {
            typedef typename get_row_iterator<std::remove_reference_t<decltype(range)>>::type IT;
            return IT(vint2(r, p1[1]), range);
          });

        typedef typename get_row_iterator<std::remove_reference_t<decltype(std::get<0>(ranges_))>>::type IT1;
        auto end0_ = IT1(vint2(r, p2[1] + 1), std::get<0>(ranges_));
        auto& cur0_ = std::get<0>(cur_);

        while (cur0_ != end0_)
        {
          internals::apply_args_star(cur_, fun);
          internals::tuple_map(cur_, [this] (auto& it) { it.next(); });
        }

        internals::apply_args_star(cur_, fun);
      }
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  parallel_for_row_wise_runner<openmp, PS...> row_wise(PS&&... params)
  {
    return parallel_for_row_wise_runner<openmp, PS...>(std::forward_as_tuple(params...));
  }

  template <typename B, typename... Params>
  class parallel_for_col_wise_runner;

  template <typename... Params>
  class parallel_for_col_wise_runner<openmp, Params...>
  {
  public:

    parallel_for_col_wise_runner(std::tuple<Params...> t) : ranges_(t) {}

    template <typename F>
    void run(F fun, bool parallel)
    {
      auto p1 = get_p1(std::get<0>(ranges_));
      auto p2 = get_p2(std::get<0>(ranges_));

      int start = p1[1];
      int end = p2[1];
#pragma omp parallel for num_threads (parallel ? omp_get_num_procs() : 0)
      for (int c = start; c <= end; c++)
      {
        auto cur_ = internals::tuple_transform(ranges_, [&] (auto& range) {
            typedef typename std::remove_reference_t<decltype(range)>::iterator IT;
            return IT(vint2(p1[0], c), range);
          });

        internals::apply_args_star(cur_, fun);
      }
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  parallel_for_col_wise_runner<openmp, PS...> col_wise(PS&&... params)
  {
    return parallel_for_col_wise_runner<openmp, PS...>(std::forward_as_tuple(params...));
  }

};

#include <vpp/core/pixel_wise.hpp>
<commit_msg>Remove useless header.<commit_after>#pragma once

#include <iostream>
#include <tuple>
#include <iod/iod.hh>
#include <iod/symbol.hh>

#include <vpp/core/imageNd.hh>
#include <vpp/core/image2d.hh>
#include <vpp/core/boxNd.hh>
#include <vpp/core/tuple_utils.hh>

#include <vpp/core/symbols.hh>
#include <vpp/core/block_wise.hh>

namespace vpp
{
  // Backends
  struct openmp {};
  struct pthreads {}; // Todo
  struct cpp_amp {}; // Todo

  template <typename T>
  struct get_row_iterator
  {
    typedef typename T::row_iterator type;
  };

  template <typename T>
  struct get_row_iterator<const T>
  {
    typedef typename T::const_row_iterator type;
  };

  template <typename T>
  using get_row_iterator_t = typename get_row_iterator<std::remove_reference_t<T>>::type;

  template <typename B, typename... Params>
  class parallel_for_pixel_wise_runner;

  using s::row_forward;
  using s::row_backward;
  using s::col_forward;
  using s::col_backward;
  using s::mem_forward;
  using s::mem_backward;
  using s::serial;
  using s::block_size;
  using s::no_threads;

  // Pixel wise takes a variable number of 2d ranges. (Todo: implement Nd version).
  // A range should respect this interface:

  //   vint2 first_point_coordinates() -> the first point coordinates of the range.
  //   vint2 last_point_coordinates() -> the last point of the range.
  //   typedefs row_iterator, const_row_iterator -> a class to iterate on each row of the range.
  //       row_iterator(vint2 p, Range range) -> build an iterator an place it at position p.
  //       operator!=(row_iterator a, row_iterator b) -> return true if a != b.
  //       operator* -> return the current object (pixel, neighborhood access, point coordinates, ...)

  template <typename OPTS, typename... Params>
  class parallel_for_pixel_wise_runner<openmp, OPTS, Params...>
  {
  public:
    typedef parallel_for_pixel_wise_runner<openmp, OPTS, Params...> self;

    parallel_for_pixel_wise_runner(std::tuple<Params...> t, OPTS opts = iod::D())
      : ranges_(t), options_(opts) {}

    template <typename F>
    void run_row_first(F fun);

    template <typename F>
    void run_col_first_parallel(F fun);

    template <typename F>
    void run(F fun, bool parallel)
    {
      if (!options_.has(col_backward) and !options_.has(col_forward))
        run_row_first(fun);
      else
        if (parallel and !options_.has(row_backward) and !options_.has(row_forward))
          run_col_first_parallel(fun);
        else
          run_row_first(fun);
    }

    template <typename ...A>
    auto operator()(A... _options)
    {
      auto new_options = iod::D(_options...);
      return parallel_for_pixel_wise_runner<openmp, decltype(new_options), Params...>
        (ranges_, new_options);
    }

    template <typename ...A>
    auto operator()(iod::iod_object<A...> _options)
    {
      auto new_options = _options;
      return parallel_for_pixel_wise_runner<openmp, decltype(new_options), Params...>
        (ranges_, new_options);
    }

    // Compute the return type of a given kernel function.
    template <typename F>
    using kernel_return_type = decltype(std::declval<F>()(*std::declval<get_row_iterator_t<Params>>()...));

    // if fun -> void.
    template <typename F,
              typename X = std::enable_if_t<std::is_same<kernel_return_type<F>, void>::value>>
    void operator|(F fun)
    {
      run(fun, true);
    }

    // if fun -> something != void.
    template <typename F,
              typename X = std::enable_if_t<!std::is_same<kernel_return_type<F>, void>::value>>
    auto operator|(F fun)
    {
      auto p1 = std::get<0>(ranges_).first_point_coordinates();
      auto p2 = std::get<0>(ranges_).last_point_coordinates();

      typedef kernel_return_type<F> fun_return_type;
      image2d<fun_return_type> out(box2d(p1, p2));
      auto ranges = std::tuple_cat(std::make_tuple(out), ranges_);
      pixel_wise(ranges)(options_) |
        [&fun] (auto& o,
                decltype(*std::declval<get_row_iterator_t<Params>>())... ps)
      { o = fun(ps...); };

      return out;
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    OPTS options_;
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  auto pixel_wise(PS&&... params)
  {
    return parallel_for_pixel_wise_runner<openmp, iod::iod_object<>, PS...>(std::forward_as_tuple(params...));
  }

  template <typename... PS>
  auto pixel_wise(std::tuple<PS...>& params)
  {
    return parallel_for_pixel_wise_runner<openmp, iod::iod_object<>, PS...>(params);
  }

  template <typename B, typename... Params>
  class parallel_for_row_wise_runner;

  template <typename... Params>
  class parallel_for_row_wise_runner<openmp, Params...>
  {
  public:

    parallel_for_row_wise_runner(std::tuple<Params...> t) : ranges_(t) {}

    template <typename F>
    void run(F fun, bool parallel)
    {
      auto p1 = get_p1(std::get<0>(ranges_));
      auto p2 = get_p2(std::get<0>(ranges_));

      int start = p1[0];
      int end = p2[0];
#pragma omp parallel for num_threads (parallel ? omp_get_num_procs() : 0)
      for (int r = start; r <= end; r++)
      {
        auto cur_ = internals::tuple_transform(ranges_, [&] (auto& range) {
            typedef typename get_row_iterator<std::remove_reference_t<decltype(range)>>::type IT;
            return IT(vint2(r, p1[1]), range);
          });

        typedef typename get_row_iterator<std::remove_reference_t<decltype(std::get<0>(ranges_))>>::type IT1;
        auto end0_ = IT1(vint2(r, p2[1] + 1), std::get<0>(ranges_));
        auto& cur0_ = std::get<0>(cur_);

        while (cur0_ != end0_)
        {
          internals::apply_args_star(cur_, fun);
          internals::tuple_map(cur_, [this] (auto& it) { it.next(); });
        }

        internals::apply_args_star(cur_, fun);
      }
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  parallel_for_row_wise_runner<openmp, PS...> row_wise(PS&&... params)
  {
    return parallel_for_row_wise_runner<openmp, PS...>(std::forward_as_tuple(params...));
  }

  template <typename B, typename... Params>
  class parallel_for_col_wise_runner;

  template <typename... Params>
  class parallel_for_col_wise_runner<openmp, Params...>
  {
  public:

    parallel_for_col_wise_runner(std::tuple<Params...> t) : ranges_(t) {}

    template <typename F>
    void run(F fun, bool parallel)
    {
      auto p1 = get_p1(std::get<0>(ranges_));
      auto p2 = get_p2(std::get<0>(ranges_));

      int start = p1[1];
      int end = p2[1];
#pragma omp parallel for num_threads (parallel ? omp_get_num_procs() : 0)
      for (int c = start; c <= end; c++)
      {
        auto cur_ = internals::tuple_transform(ranges_, [&] (auto& range) {
            typedef typename std::remove_reference_t<decltype(range)>::iterator IT;
            return IT(vint2(p1[0], c), range);
          });

        internals::apply_args_star(cur_, fun);
      }
    }

    template <typename F>
    void operator<<(F fun)
    {
      run(fun, true);
    }

    template <typename F>
    void operator<(F fun)
    {
      run(fun, false);
    }

  private:
    std::tuple<Params...> ranges_;
  };

  template <typename... PS>
  parallel_for_col_wise_runner<openmp, PS...> col_wise(PS&&... params)
  {
    return parallel_for_col_wise_runner<openmp, PS...>(std::forward_as_tuple(params...));
  }

};

#include <vpp/core/pixel_wise.hpp>
<|endoftext|>


<commit_before>/** \file    add_subsystem_tags.cc
 *  \brief   Add additional tags for interfaces to identitify subset views of
             IxTheo like RelBib and Bibstudies
 *  \author  Johannes Riedl
 */

/*
    Copyright (C) 2018, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <iostream>
#include <map>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "File.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "util.h"

namespace {

const std::string RELBIB_TAG("REL");
const std::string BIBSTUDIES_TAG("BIB");

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

bool HasRelBibSSGN(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("084")) {
        const MARC::Subfields subfields(field.getSubfields());
        if (subfields.hasSubfieldWithValue('2', "ssgn") and subfields.hasSubfieldWithValue('a', "0"))
            return true;
    }
    return false;
}

bool HasRelBibIxTheoNotation(const MARC::Record &record) {
    // Integrate IxTheo Notations A*.B*,T*,V*,X*,Z*
    static const std::string RELBIB_IXTHEO_NOTATION_PATTERN("^[ABTVXZ][A-Z].*|.*:[ABTVXZ][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(RELBIB_IXTHEO_NOTATION_PATTERN));
    for (const auto& field : record.getTagRange("652")) {
        for (const auto& subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfieldA))
                return true;
        }
    }
    return false;
}

bool HasPlausibleDDCPrefix(const std::string ddc_string) {
    // Exclude records that are obviously not DDC
    static const std::string PLAUSIBLE_DDC_PREFIX_PATTERN("^\\d\\d");
    static RegexMatcher * const plausible_ddc_prefix_matcher(RegexMatcher::RegexMatcherFactoryOrDie(PLAUSIBLE_DDC_PREFIX_PATTERN));
    return plausible_ddc_prefix_matcher->matched(ddc_string);
}

// Additional criteria that lead that prevent the exclusion of a record that has a 220-289 field
bool HasAdditionalRelbibAdmitDDC(const MARC::Record &record) {
    static const std::string RELBIB_ADMIT_DDC_PATTERN("^([12][01][0-9]|2[9][0-9]|[3-9][0-9][0-9]).*$");
    static RegexMatcher * const relbib_admit_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_ADMIT_DDC_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields("a")) {
            if (HasPlausibleDDCPrefix(subfieldA) and relbib_admit_ddc_range_matcher->matched(subfieldA))
                return true;
        }
    }
    return false;
}

bool HasRelBibExcludeDDC(const MARC::Record &record) {
    if (not record.hasTag("082"))
        return true;
    // Exclude DDC 220-289
    static const std::string RELBIB_EXCLUDE_DDC_RANGE_PATTERN("^2[2-8][0-9](/|\\.){0,2}[^.]*$");
//    static const std::string RELBIB_EXCLUDE_DDC_RANGE_PATTERN("^2[2-8][0-9][/.]?[^.]*$");
    static RegexMatcher * const relbib_exclude_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_RANGE_PATTERN));

    // Make sure we have 082-fields to examine
    if (not record.hasTag("082"))
        return false;

    // In general we exclude if the exclude range is matched
    // But we include it anyway if we find another reasonable DDC-code
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_exclude_ddc_range_matcher->matched(subfieldA)) {
                if (not HasAdditionalRelbibAdmitDDC(record))
                    return true;
            }
        }
    }

    // Exclude item if it has only DDC a 400 or 800 DDC notation
    static const std::string RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN("^[48][0-9][0-9]$");
    static RegexMatcher * const relbib_exclude_ddc_categories_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields('a')) {
            if (HasPlausibleDDCPrefix(subfieldA) and not relbib_exclude_ddc_categories_matcher->matched(subfieldA))
                return false;
        }
    }
    return true;
}

bool MatchesRelBibDDC(const MARC::Record &record) {
    return not HasRelBibExcludeDDC(record);
}

bool IsDefinitelyRelBib(const MARC::Record &record) {
   return HasRelBibSSGN(record) or HasRelBibIxTheoNotation(record) or MatchesRelBibDDC(record);
}

bool IsProbablyRelBib(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("191")) {
        for (const auto& subfield : field.getSubfields().extractSubfields("a")) {
            if (subfield == "1")
                return true;
        }
    }
    return false;
}

std::set<std::string> GetTemporarySuperiorRelBibList() {
    const std::string relbib_superior_temporary_file("/usr/local/ub_tools/cpp/data/relbib_superior_temporary.txt");
    std::set<std::string> superior_temporary_list;
    File superior_temporary(relbib_superior_temporary_file, "r");
    std::string line;
    while (superior_temporary.getline(&line) and not superior_temporary.eof())
        superior_temporary_list.emplace(line);
    return superior_temporary_list;
}

bool IsTemporaryRelBibSuperior(const MARC::Record &record) {
    static std::set<std::string> superior_temporary_list(GetTemporarySuperiorRelBibList());
    if (superior_temporary_list.find(record.getControlNumber()) != superior_temporary_list.end())
        return true;
    return false;
}

bool ExcludeBecauseOfRWEX(const MARC::Record &record) {
    for (const auto &field : record.getTagRange("LOK")) {
        const auto &subfields(field.getSubfields());
        for (const auto &subfield0: subfields.extractSubfields('0')) {
            if (not StringUtil::StartsWith(subfield0, "935"))
                continue;
            for (const auto &subfieldA : subfields.extractSubfields('a')) {
                if (subfieldA == "rwex")
                    return true;
            }
        }
    }
    return false;
}

bool IsRelBibRecord(const MARC::Record &record) {
    return ((IsDefinitelyRelBib(record) or
             IsProbablyRelBib(record) or
             IsTemporaryRelBibSuperior(record))
             and not ExcludeBecauseOfRWEX(record));
}

bool HasBibStudiesIxTheoNotation(const MARC::Record &record) {
    static const std::string BIBSTUDIES_IXTHEO_PATTERN("^[H][A-Z].*|.*:[H][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(BIBSTUDIES_IXTHEO_PATTERN));
    for (const auto &field : record.getTagRange("652")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfieldA))
                return true;
        }
    }
    return false;
}

bool IsBibStudiesRecord(const MARC::Record &record) {
    return HasBibStudiesIxTheoNotation(record);
}

void AddSubsystemTag(MARC::Record * const record, const std::string &tag) {
    // Don't insert twice
    if (record->getFirstField(tag) != record->end())
        return;
    MARC::Subfields subfields;
    subfields.addSubfield('a', "1");
    record->insertField(tag, subfields);
}

void AddSubsystemTags(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer) {
    unsigned int record_count(0), modified_count(0);
    while (MARC::Record record = marc_reader->read()) {
        ++record_count;
        bool modified_record(false);
        if (IsRelBibRecord(record)) {
            AddSubsystemTag(&record, RELBIB_TAG);
            modified_record = true;
        }
        if (IsBibStudiesRecord(record)) {
            AddSubsystemTag(&record, BIBSTUDIES_TAG);
            modified_record = true;
        }
        marc_writer->write(record);
        if (modified_record)
            ++modified_count;
    }

    LOG_INFO("Modified " + std::to_string(modified_count) + " of " + std::to_string(record_count) + " records.");
}

} //unnamed namespace

int Main(int argc, char **argv) {
    if (argc != 3)
        Usage();

    const std::string marc_input_filename(argv[1]);
    const std::string marc_output_filename(argv[2]);
    if (unlikely(marc_input_filename == marc_output_filename))
        LOG_ERROR("Title data input file name equals output file name!");

    std::unique_ptr<MARC::Reader> marc_reader(MARC::Reader::Factory(marc_input_filename));
    std::unique_ptr<MARC::Writer> marc_writer(MARC::Writer::Factory(marc_output_filename));
    AddSubsystemTags(marc_reader.get() , marc_writer.get());

    return EXIT_SUCCESS;
}
<commit_msg>Set back to current live version<commit_after>/** \file    add_subsystem_tags.cc
 *  \brief   Add additional tags for interfaces to identitify subset views of
             IxTheo like RelBib and Bibstudies
 *  \author  Johannes Riedl
 */

/*
    Copyright (C) 2018, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <iostream>
#include <map>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "File.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "util.h"

namespace {

const std::string RELBIB_TAG("REL");
const std::string BIBSTUDIES_TAG("BIB");

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

bool HasRelBibSSGN(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("084")) {
        const MARC::Subfields subfields(field.getSubfields());
        if (subfields.hasSubfieldWithValue('2', "ssgn") and subfields.hasSubfieldWithValue('a', "0"))
            return true;
    }
    return false;
}

bool HasRelBibIxTheoNotation(const MARC::Record &record) {
    // Integrate IxTheo Notations A*.B*,T*,V*,X*,Z*
    static const std::string RELBIB_IXTHEO_NOTATION_PATTERN("^[ABTVXZ][A-Z].*|.*:[ABTVXZ][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(RELBIB_IXTHEO_NOTATION_PATTERN));
    for (const auto& field : record.getTagRange("652")) {
        for (const auto& subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfieldA))
                return true;
        }
    }
    return false;
}

bool HasRelBibExcludeDDC(const MARC::Record &record) {
    if (not record.hasTag("082"))
        return true;
    // Exclude records that are obviously not DDC
    static const std::string PLAUSIBLE_DDC_START_PATTERN("^\\d\\d");
    static RegexMatcher * const plausible_ddc_start_matcher(RegexMatcher::RegexMatcherFactoryOrDie(PLAUSIBLE_DDC_START_PATTERN));
    // Exclude DDC 220-289, i.e. do not include if a DDC code of this range occurs anywhere in the DDC code
    static const std::string RELBIB_EXCLUDE_DDC_RANGE_PATTERN("^2[2-8][0-9][/.]?[^.]*$");
    static RegexMatcher * const relbib_exclude_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_RANGE_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_exclude_ddc_range_matcher->matched(subfieldA))
                return true;
        }
    }
    // Exclude item if it has only DDC a 400 or 800 DDC notation
    static const std::string RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN("^[48][0-9][0-9]$");
    static RegexMatcher * const relbib_exclude_ddc_categories_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields('a')) {
            if (plausible_ddc_start_matcher->matched(subfieldA) and not relbib_exclude_ddc_categories_matcher->matched(subfieldA))
                return false;
        }
    }
    return true;
}

bool MatchesRelBibDDC(const MARC::Record &record) {
    return not HasRelBibExcludeDDC(record);
}

bool IsDefinitelyRelBib(const MARC::Record &record) {
   return HasRelBibSSGN(record) or HasRelBibIxTheoNotation(record) or MatchesRelBibDDC(record);
}

bool IsProbablyRelBib(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("191")) {
        for (const auto& subfield : field.getSubfields().extractSubfields("a")) {
            if (subfield == "1")
                return true;
        }
    }
    return false;
}

std::set<std::string> GetTemporarySuperiorRelBibList() {
    const std::string relbib_superior_temporary_file("/usr/local/ub_tools/cpp/data/relbib_superior_temporary.txt");
    std::set<std::string> superior_temporary_list;
    File superior_temporary(relbib_superior_temporary_file, "r");
    std::string line;
    while (superior_temporary.getline(&line) and not superior_temporary.eof())
        superior_temporary_list.emplace(line);
    return superior_temporary_list;
}

bool IsTemporaryRelBibSuperior(const MARC::Record &record) {
    static std::set<std::string> superior_temporary_list(GetTemporarySuperiorRelBibList());
    if (superior_temporary_list.find(record.getControlNumber()) != superior_temporary_list.end())
        return true;
    return false;
}

bool ExcludeBecauseOfRWEX(const MARC::Record &record) {
    for (const auto &field : record.getTagRange("LOK")) {
        const auto &subfields(field.getSubfields());
        for (const auto &subfield0: subfields.extractSubfields('0')) {
            if (not StringUtil::StartsWith(subfield0, "935"))
                continue;
            for (const auto &subfieldA : subfields.extractSubfields('a')) {
                if (subfieldA == "rwex")
                    return true;
            }
        }
    }
    return false;
}

bool IsRelBibRecord(const MARC::Record &record) {
    return ((IsDefinitelyRelBib(record) or
             IsProbablyRelBib(record) or
             IsTemporaryRelBibSuperior(record))
             and not ExcludeBecauseOfRWEX(record));
}

bool HasBibStudiesIxTheoNotation(const MARC::Record &record) {
    static const std::string BIBSTUDIES_IXTHEO_PATTERN("^[H][A-Z].*|.*:[H][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(BIBSTUDIES_IXTHEO_PATTERN));
    for (const auto &field : record.getTagRange("652")) {
        for (const auto &subfieldA : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfieldA))
                return true;
        }
    }
    return false;
}

bool IsBibStudiesRecord(const MARC::Record &record) {
    return HasBibStudiesIxTheoNotation(record);
}

void AddSubsystemTag(MARC::Record * const record, const std::string &tag) {
    // Don't insert twice
    if (record->getFirstField(tag) != record->end())
        return;
    MARC::Subfields subfields;
    subfields.addSubfield('a', "1");
    record->insertField(tag, subfields);
}

void AddSubsystemTags(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer) {
    unsigned int record_count(0), modified_count(0);
    while (MARC::Record record = marc_reader->read()) {
        ++record_count;
        bool modified_record(false);
        if (IsRelBibRecord(record)) {
            AddSubsystemTag(&record, RELBIB_TAG);
            modified_record = true;
        }
        if (IsBibStudiesRecord(record)) {
            AddSubsystemTag(&record, BIBSTUDIES_TAG);
            modified_record = true;
        }
        marc_writer->write(record);
        if (modified_record)
            ++modified_count;
    }

    LOG_INFO("Modified " + std::to_string(modified_count) + " of " + std::to_string(record_count) + " records.");
}

} //unnamed namespace

int Main(int argc, char **argv) {
    if (argc != 3)
        Usage();

    const std::string marc_input_filename(argv[1]);
    const std::string marc_output_filename(argv[2]);
    if (unlikely(marc_input_filename == marc_output_filename))
        LOG_ERROR("Title data input file name equals output file name!");

    std::unique_ptr<MARC::Reader> marc_reader(MARC::Reader::Factory(marc_input_filename));
    std::unique_ptr<MARC::Writer> marc_writer(MARC::Writer::Factory(marc_output_filename));
    AddSubsystemTags(marc_reader.get() , marc_writer.get());

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/** \file    add_subsystem_tags.cc
 *  \brief   Add additional tags for interfaces to identitify subset views of
             IxTheo like RelBib and Bibstudies
 *  \author  Johannes Riedl
 */

/*
    Copyright (C) 2018, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <iostream>
#include <map>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "File.h"
#include "FileUtil.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "util.h"

namespace {

const std::string RELBIB_TAG("REL");
const std::string BIBSTUDIES_TAG("BIB");

enum SubSystem { RELBIB, BIBSTUDIES };
const unsigned NUM_OF_SUBSYSTEMS(2);

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

bool HasRelBibSSGN(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("084")) {
        const MARC::Subfields subfields(field.getSubfields());
        if (subfields.hasSubfieldWithValue('2', "ssgn") and subfields.hasSubfieldWithValue('a', "0"))
            return true;
    }
    return false;
}

bool HasRelBibIxTheoNotation(const MARC::Record &record) {
    // Integrate IxTheo Notations A*.B*,T*,V*,X*,Z*
    static const std::string RELBIB_IXTHEO_NOTATION_PATTERN("^[ABTVXZ][A-Z].*|.*:[ABTVXZ][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(RELBIB_IXTHEO_NOTATION_PATTERN));
    for (const auto& field : record.getTagRange("652")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool HasPlausibleDDCPrefix(const std::string &ddc_string) {
    // Exclude records that where the entry in the DCC field is not plausible
    static const std::string PLAUSIBLE_DDC_PREFIX_PATTERN("^\\d\\d");
    static RegexMatcher * const plausible_ddc_prefix_matcher(RegexMatcher::RegexMatcherFactoryOrDie(PLAUSIBLE_DDC_PREFIX_PATTERN));
    return plausible_ddc_prefix_matcher->matched(ddc_string);
}

// Additional criteria that prevent the exclusion of a record that has a 220-289 field
bool HasAdditionalRelbibAdmissionDDC(const MARC::Record &record) {
    static const std::string RELBIB_ADMIT_DDC_PATTERN("^([12][01][0-9]|2[9][0-9]|[3-9][0-9][0-9]).*$");
    static RegexMatcher * const relbib_admit_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_ADMIT_DDC_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (HasPlausibleDDCPrefix(subfield_a) and relbib_admit_ddc_range_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool HasRelBibExcludeDDC(const MARC::Record &record) {
    if (not record.hasTag("082"))
        return true;
    // Exclude DDC 220-289
    static const std::string RELBIB_EXCLUDE_DDC_RANGE_PATTERN("^2[2-8][0-9](/|\\.){0,2}[^.]*$");
    static RegexMatcher * const relbib_exclude_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_RANGE_PATTERN));

    // Make sure we have 082-fields to examine
    if (not record.hasTag("082"))
        return false;

    // In general we exclude if the exclude range is matched
    // But we include it anyway if we find another reasonable DDC-code
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_exclude_ddc_range_matcher->matched(subfield_a)) {
                if (not HasAdditionalRelbibAdmissionDDC(record))
                    return true;
            }
        }
    }

    // Exclude item if it has only a 400 or 800 DDC notation
    static const std::string RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN("^[48][0-9][0-9]$");
    static RegexMatcher * const relbib_exclude_ddc_categories_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields('a')) {
            if (HasPlausibleDDCPrefix(subfield_a) and not relbib_exclude_ddc_categories_matcher->matched(subfield_a))
                return false;
        }
    }
    return true;
}

bool MatchesRelBibDDC(const MARC::Record &record) {
    return not HasRelBibExcludeDDC(record);
}

bool IsDefinitelyRelBib(const MARC::Record &record) {
   return HasRelBibSSGN(record) or HasRelBibIxTheoNotation(record) or MatchesRelBibDDC(record);
}

bool IsProbablyRelBib(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("191")) {
        for (const auto& subfield : field.getSubfields().extractSubfields("a")) {
            if (subfield == "1")
                return true;
        }
    }
    return false;
}

std::set<std::string> GetTemporarySuperiorRelBibList() {
    const std::string relbib_superior_temporary_file("/usr/local/ub_tools/cpp/data/relbib_superior_temporary.txt");
    std::set<std::string> superior_temporary_list;
    File superior_temporary(relbib_superior_temporary_file, "r");
    std::string line;
    while (superior_temporary.getline(&line) and not superior_temporary.eof())
        superior_temporary_list.emplace(line);
    return superior_temporary_list;
}

bool IsTemporaryRelBibSuperior(const MARC::Record &record) {
    static std::set<std::string> superior_temporary_list(GetTemporarySuperiorRelBibList());
    if (superior_temporary_list.find(record.getControlNumber()) != superior_temporary_list.end())
        return true;
    return false;
}

bool ExcludeBecauseOfRWEX(const MARC::Record &record) {
    for (const auto &field : record.getTagRange("LOK")) {
        const auto &subfields(field.getSubfields());
        for (const auto &subfield0: subfields.extractSubfields('0')) {
            if (not StringUtil::StartsWith(subfield0, "935"))
                continue;
            for (const auto &subfield_a : subfields.extractSubfields('a')) {
                if (subfield_a == "rwex")
                    return true;
            }
        }
    }
    return false;
}

bool IsRelBibRecord(const MARC::Record &record) {
    return ((IsDefinitelyRelBib(record) or
             IsProbablyRelBib(record) or
             IsTemporaryRelBibSuperior(record))
             and not ExcludeBecauseOfRWEX(record));
}

bool HasBibStudiesIxTheoNotation(const MARC::Record &record) {
    static const std::string BIBSTUDIES_IXTHEO_PATTERN("^[H][A-Z].*|.*:[H][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(BIBSTUDIES_IXTHEO_PATTERN));
    for (const auto &field : record.getTagRange("652")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool IsBibStudiesRecord(const MARC::Record &record) {
    return HasBibStudiesIxTheoNotation(record);
}

void AddSubsystemTag(MARC::Record * const record, const std::string &tag) {
    // Don't insert twice
    if (record->getFirstField(tag) != record->end())
        return;
    MARC::Subfields subfields;
    subfields.addSubfield('a', "1");
    record->insertField(tag, subfields);
}

void CollectSuperiorOrParallelWorks(const MARC::Record &record, std::unordered_set<std::string> * const superior_or_parallel_works) {
    const std::set<std::string> parallel(MARC::ExtractCrossReferencePPNs(record));
    superior_or_parallel_works->insert(parallel.begin(), parallel.end());
    superior_or_parallel_works->insert(record.getSuperiorControlNumber());
}

// Get set of immediately belonging or superior or parallel records
void GetSubsystemPPNSet(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer,
                      std::vector<std::unordered_set<std::string>> * const subsystem_sets) {
    while (const MARC::Record record = marc_reader->read()) {
        if (IsRelBibRecord(record)) {
            ((*subsystem_sets)[RELBIB]).emplace(record.getControlNumber());
            CollectSuperiorOrParallelWorks(record, &((*subsystem_sets)[RELBIB]));
        }
        if (IsBibStudiesRecord(record)) {
            ((*subsystem_sets)[BIBSTUDIES]).emplace(record.getControlNumber());
            CollectSuperiorOrParallelWorks(record, &((*subsystem_sets)[BIBSTUDIES]));
        }
        marc_writer->write(record);
    }
}

void AddSubsystemTags(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer,
                      const std::vector<std::unordered_set<std::string>> &subsystem_sets) {
    unsigned record_count(0), modified_count(0);
    while (MARC::Record record = marc_reader->read()) {
        ++record_count;
        bool modified_record(false);
        if ((subsystem_sets[RELBIB]).find(record.getControlNumber()) != subsystem_sets[RELBIB].end()) {
            AddSubsystemTag(&record, RELBIB_TAG);
            modified_record = true;
        }
        if ((subsystem_sets[BIBSTUDIES]).find(record.getControlNumber()) != subsystem_sets[RELBIB].end()) {
            AddSubsystemTag(&record, BIBSTUDIES_TAG);
            modified_record = true;
        }
        if (modified_record)
            ++modified_count;
         marc_writer->write(record);

    }
    LOG_INFO("Modified " + std::to_string(modified_count) + " of " + std::to_string(record_count) + " records.");
}

void InitializeSubsystemPPNSets(std::vector<std::unordered_set<std::string>> * const subsystem_ppn_sets) {
    for (unsigned i = 0; i < NUM_OF_SUBSYSTEMS; ++i)
        subsystem_ppn_sets->push_back(std::unordered_set<std::string>());
}

} //unnamed namespace

int Main(int argc, char **argv) {
    if (argc != 3)
        Usage();

    const std::string marc_input_filename(argv[1]);
    const std::string marc_output_filename(argv[2]);
    if (unlikely(marc_input_filename == marc_output_filename))
        LOG_ERROR("Title data input file name equals output file name!");

    std::vector<std::unordered_set<std::string>> subsystem_sets;
    InitializeSubsystemPPNSets(&subsystem_sets);

    std::unique_ptr<MARC::Reader> marc_reader(MARC::Reader::Factory(marc_input_filename));
    FileUtil::AutoTempFile tmp_marc_file("/dev/shm/", ".mrc");
    std::unique_ptr<MARC::Writer> marc_tmp_writer(MARC::Writer::Factory(tmp_marc_file.getFilePath()));
    GetSubsystemPPNSet(marc_reader.get(), marc_tmp_writer.get(), &subsystem_sets);
    if (not marc_tmp_writer->flush())
        LOG_ERROR("Could not flush to " + tmp_marc_file.getFilePath());
    std::unique_ptr<MARC::Reader> marc_tmp_reader(MARC::Reader::Factory(tmp_marc_file.getFilePath()));
    std::unique_ptr<MARC::Writer> marc_writer(MARC::Writer::Factory(marc_output_filename));
    AddSubsystemTags(marc_tmp_reader.get(), marc_writer.get(), subsystem_sets);

    return EXIT_SUCCESS;
}
<commit_msg>Requested changes<commit_after>/** \file    add_subsystem_tags.cc
 *  \brief   Add additional tags for interfaces to identitify subset views of
             IxTheo like RelBib and Bibstudies
 *  \author  Johannes Riedl
 */

/*
    Copyright (C) 2018, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <fstream>
#include <iostream>
#include <map>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "File.h"
#include "FileUtil.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "util.h"

namespace {

const std::string RELBIB_TAG("REL");
const std::string BIBSTUDIES_TAG("BIB");

enum SubSystem { RELBIB, BIBSTUDIES };
const unsigned NUM_OF_SUBSYSTEMS(2);

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

bool HasRelBibSSGN(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("084")) {
        const MARC::Subfields subfields(field.getSubfields());
        if (subfields.hasSubfieldWithValue('2', "ssgn") and subfields.hasSubfieldWithValue('a', "0"))
            return true;
    }
    return false;
}

bool HasRelBibIxTheoNotation(const MARC::Record &record) {
    // Integrate IxTheo Notations A*.B*,T*,V*,X*,Z*
    static const std::string RELBIB_IXTHEO_NOTATION_PATTERN("^[ABTVXZ][A-Z].*|.*:[ABTVXZ][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(RELBIB_IXTHEO_NOTATION_PATTERN));
    for (const auto& field : record.getTagRange("652")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool HasPlausibleDDCPrefix(const std::string &ddc_string) {
    // Exclude records that where the entry in the DCC field is not plausible
    static const std::string PLAUSIBLE_DDC_PREFIX_PATTERN("^\\d\\d");
    static RegexMatcher * const plausible_ddc_prefix_matcher(RegexMatcher::RegexMatcherFactoryOrDie(PLAUSIBLE_DDC_PREFIX_PATTERN));
    return plausible_ddc_prefix_matcher->matched(ddc_string);
}

// Additional criteria that prevent the exclusion of a record that has a 220-289 field
bool HasAdditionalRelbibAdmissionDDC(const MARC::Record &record) {
    static const std::string RELBIB_ADMIT_DDC_PATTERN("^([12][01][0-9]|2[9][0-9]|[3-9][0-9][0-9]).*$");
    static RegexMatcher * const relbib_admit_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_ADMIT_DDC_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (HasPlausibleDDCPrefix(subfield_a) and relbib_admit_ddc_range_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool HasRelBibExcludeDDC(const MARC::Record &record) {
    if (not record.hasTag("082"))
        return true;
    // Exclude DDC 220-289
    static const std::string RELBIB_EXCLUDE_DDC_RANGE_PATTERN("^2[2-8][0-9](/|\\.){0,2}[^.]*$");
    static RegexMatcher * const relbib_exclude_ddc_range_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_RANGE_PATTERN));

    // Make sure we have 082-fields to examine
    if (not record.hasTag("082"))
        return false;

    // In general we exclude if the exclude range is matched
    // But we include it anyway if we find another reasonable DDC-code
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_exclude_ddc_range_matcher->matched(subfield_a)) {
                if (not HasAdditionalRelbibAdmissionDDC(record))
                    return true;
            }
        }
    }

    // Exclude item if it has only a 400 or 800 DDC notation
    static const std::string RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN("^[48][0-9][0-9]$");
    static RegexMatcher * const relbib_exclude_ddc_categories_matcher(RegexMatcher::RegexMatcherFactoryOrDie(RELBIB_EXCLUDE_DDC_CATEGORIES_PATTERN));
    for (const auto &field : record.getTagRange("082")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields('a')) {
            if (HasPlausibleDDCPrefix(subfield_a) and not relbib_exclude_ddc_categories_matcher->matched(subfield_a))
                return false;
        }
    }
    return true;
}

bool MatchesRelBibDDC(const MARC::Record &record) {
    return not HasRelBibExcludeDDC(record);
}

bool IsDefinitelyRelBib(const MARC::Record &record) {
   return HasRelBibSSGN(record) or HasRelBibIxTheoNotation(record) or MatchesRelBibDDC(record);
}

bool IsProbablyRelBib(const MARC::Record &record) {
    for (const auto& field : record.getTagRange("191")) {
        for (const auto& subfield : field.getSubfields().extractSubfields("a")) {
            if (subfield == "1")
                return true;
        }
    }
    return false;
}

std::set<std::string> GetTemporarySuperiorRelBibList() {
    const std::string relbib_superior_temporary_file("/usr/local/ub_tools/cpp/data/relbib_superior_temporary.txt");
    std::set<std::string> superior_temporary_list;
    File superior_temporary(relbib_superior_temporary_file, "r");
    std::string line;
    while (superior_temporary.getline(&line) and not superior_temporary.eof())
        superior_temporary_list.emplace(line);
    return superior_temporary_list;
}

bool IsTemporaryRelBibSuperior(const MARC::Record &record) {
    static std::set<std::string> superior_temporary_list(GetTemporarySuperiorRelBibList());
    if (superior_temporary_list.find(record.getControlNumber()) != superior_temporary_list.end())
        return true;
    return false;
}

bool ExcludeBecauseOfRWEX(const MARC::Record &record) {
    for (const auto &field : record.getTagRange("LOK")) {
        const auto &subfields(field.getSubfields());
        for (const auto &subfield0: subfields.extractSubfields('0')) {
            if (not StringUtil::StartsWith(subfield0, "935"))
                continue;
            for (const auto &subfield_a : subfields.extractSubfields('a')) {
                if (subfield_a == "rwex")
                    return true;
            }
        }
    }
    return false;
}

bool IsRelBibRecord(const MARC::Record &record) {
    return ((IsDefinitelyRelBib(record) or
             IsProbablyRelBib(record) or
             IsTemporaryRelBibSuperior(record))
             and not ExcludeBecauseOfRWEX(record));
}

bool HasBibStudiesIxTheoNotation(const MARC::Record &record) {
    static const std::string BIBSTUDIES_IXTHEO_PATTERN("^[H][A-Z].*|.*:[H][A-Z].*");
    static RegexMatcher * const relbib_ixtheo_notations_matcher(RegexMatcher::RegexMatcherFactory(BIBSTUDIES_IXTHEO_PATTERN));
    for (const auto &field : record.getTagRange("652")) {
        for (const auto &subfield_a : field.getSubfields().extractSubfields("a")) {
            if (relbib_ixtheo_notations_matcher->matched(subfield_a))
                return true;
        }
    }
    return false;
}

bool IsBibStudiesRecord(const MARC::Record &record) {
    return HasBibStudiesIxTheoNotation(record);
}

void AddSubsystemTag(MARC::Record * const record, const std::string &tag) {
    // Don't insert twice
    if (record->getFirstField(tag) != record->end())
        return;
    MARC::Subfields subfields;
    subfields.addSubfield('a', "1");
    record->insertField(tag, subfields);
}

void CollectSuperiorOrParallelWorks(const MARC::Record &record, std::unordered_set<std::string> * const superior_or_parallel_works) {
    const std::set<std::string> parallel(MARC::ExtractCrossReferencePPNs(record));
    superior_or_parallel_works->insert(parallel.begin(), parallel.end());
    superior_or_parallel_works->insert(record.getSuperiorControlNumber());
}

// Get set of immediately belonging or superior or parallel records
void GetSubsystemPPNSet(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer,
                      std::vector<std::unordered_set<std::string>> * const subsystem_sets) {
    while (const MARC::Record record = marc_reader->read()) {
        if (IsRelBibRecord(record)) {
            ((*subsystem_sets)[RELBIB]).emplace(record.getControlNumber());
            CollectSuperiorOrParallelWorks(record, &((*subsystem_sets)[RELBIB]));
        }
        if (IsBibStudiesRecord(record)) {
            ((*subsystem_sets)[BIBSTUDIES]).emplace(record.getControlNumber());
            CollectSuperiorOrParallelWorks(record, &((*subsystem_sets)[BIBSTUDIES]));
        }
        marc_writer->write(record);
    }
}

void AddSubsystemTags(MARC::Reader * const marc_reader, MARC::Writer * const marc_writer,
                      const std::vector<std::unordered_set<std::string>> &subsystem_sets) {
    unsigned record_count(0), modified_count(0);
    while (MARC::Record record = marc_reader->read()) {
        ++record_count;
        bool modified_record(false);
        if ((subsystem_sets[RELBIB]).find(record.getControlNumber()) != subsystem_sets[RELBIB].end()) {
            AddSubsystemTag(&record, RELBIB_TAG);
            modified_record = true;
        }
        if ((subsystem_sets[BIBSTUDIES]).find(record.getControlNumber()) != subsystem_sets[RELBIB].end()) {
            AddSubsystemTag(&record, BIBSTUDIES_TAG);
            modified_record = true;
        }
        if (modified_record)
            ++modified_count;
         marc_writer->write(record);
    }
    LOG_INFO("Modified " + std::to_string(modified_count) + " of " + std::to_string(record_count) + " records.");
}

void InitializeSubsystemPPNSets(std::vector<std::unordered_set<std::string>> * const subsystem_ppn_sets) {
    for (unsigned i(0); i < NUM_OF_SUBSYSTEMS; ++i)
        subsystem_ppn_sets->push_back(std::unordered_set<std::string>());
}

} //unnamed namespace

int Main(int argc, char **argv) {
    if (argc != 3)
        Usage();

    const std::string marc_input_filename(argv[1]);
    const std::string marc_output_filename(argv[2]);
    if (unlikely(marc_input_filename == marc_output_filename))
        LOG_ERROR("Title data input file name equals output file name!");

    std::vector<std::unordered_set<std::string>> subsystem_sets;
    InitializeSubsystemPPNSets(&subsystem_sets);

    std::unique_ptr<MARC::Reader> marc_reader(MARC::Reader::Factory(marc_input_filename));
    FileUtil::AutoTempFile tmp_marc_file("/dev/shm/", ".mrc");
    std::unique_ptr<MARC::Writer> marc_tmp_writer(MARC::Writer::Factory(tmp_marc_file.getFilePath()));
    GetSubsystemPPNSet(marc_reader.get(), marc_tmp_writer.get(), &subsystem_sets);
    if (not marc_tmp_writer->flush())
        LOG_ERROR("Could not flush to " + tmp_marc_file.getFilePath());
    std::unique_ptr<MARC::Reader> marc_tmp_reader(MARC::Reader::Factory(tmp_marc_file.getFilePath()));
    std::unique_ptr<MARC::Writer> marc_writer(MARC::Writer::Factory(marc_output_filename));
    AddSubsystemTags(marc_tmp_reader.get(), marc_writer.get(), subsystem_sets);

    return EXIT_SUCCESS;
}
<|endoftext|>