2 #include "tmeditorwidget.h"
10 #include <QMutexLocker>
12 #include <QJsonDocument>
13 #include <QJsonObject>
18 // HtmlData -------------------------------------------------------------------
20 TM::HtmlData::HtmlData(HtmlNode node_, int begin_, int tail_)
21 : RangeData(begin_, tail_)
26 int TM::HtmlData::type() const { return Type; }
28 HtmlNode TM::HtmlData::node() { return m_node; }
30 QString TM::HtmlData::debug_dump() const
33 result += "[HtmlData:";
34 result += m_node.name() + ",";
35 result += QString::number(begin()) + ",";
36 result += QString::number(tail()) + "]";
40 TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_)
42 return pointer(new HtmlData(node_, begin_, tail_));
45 // TextConverter --------------------------------------------------------------
48 * \brief 引数として与えられたHtmlの範囲から構造化テキストを作成します。
50 Text::pointer TM::TextConverter::to_text(HtmlRange range)
53 Text::pointer result = Text::create();
56 for(HtmlNode const &node : range)
58 if(node.type() != HtmlNode::Text) continue;
59 Text::pointer p = stuff_text(result, node.value());
61 int num = p->string().size();
62 HtmlData::pointer hd = HtmlData::create(node, begin, begin + num - 1);
70 * \brief 引数として与えられたノードから構造化テキストを作成します。
73 * 無効なノードが与えられた場合、空の構造化テキストを返します。
75 Text::pointer TM::TextConverter::stuff_text(Text::pointer parent, QString const &string)
78 for(QChar const &ch : string)
80 if(is_ignorable_white_space(ch)) continue;
81 if(is_white_space(ch)) outstring.append(' ');
82 else outstring.append(ch);
84 if(outstring.isEmpty()) return Text::pointer();
86 Text::pointer result = Text::create(parent, outstring);
87 parent->append(result);
92 * \brief 連続する空白文字を検出します。
94 * 最初の空白文字、あるいは空白文字以外は、falseを返します。
95 * 連続する空白文字の二番目以降に対してのみtrueを返します。
97 bool TM::TextConverter::is_ignorable_white_space(const QChar &ch)
99 if(!is_white_space(ch))
113 * \brief 引数として与えられたchが空白文字の場合true、それ以外の場合falseを返します。
115 bool TM::TextConverter::is_white_space(QChar const &ch)
130 // HtmlConverter --------------------------------------------------------------
133 void TM::HtmlConverter::append(QString string)
135 HtmlText::pointer text = HtmlText::create(HtmlText::weak_pointer());
136 text->set_value(string);
137 m_nodes.append(text);
140 void TM::HtmlConverter::append(HtmlNode::pointer node, QString string)
142 QList<HtmlNode::pointer> left, right;
143 for(HtmlNode hn = node->parent(); hn; hn = hn.parent())
145 if(hn.tname() == "body") break;
146 left.prepend(hn.lself());
147 right.append(hn.ltail());
150 m_nodes.append(left);
152 m_nodes.append(right);
155 QString TM::HtmlConverter::to_string()
160 for(Html::pointer node : m_nodes) result += node->to_string();
165 void TM::HtmlConverter::adjust()
171 for(int i = 0; i < m_nodes.size() - 1; i++)
173 HtmlNode::pointer p1 = m_nodes.at(i);
174 if(p1->type() != Html::Element || p1->place() != Html::Close) continue;
175 HtmlNode::pointer p2 = m_nodes.at(i + 1);
176 if(p2->type() != Html::Element || p2->place() != Html::Open) continue;
178 if(p1->lbegin() == p2->lbegin())
189 // TextSegment ----------------------------------------------------------------
191 TM::TextSegment::TextSegment(
192 Service *service, int scode, int segment_id, QString source)
193 : m_segment_id(segment_id)
194 , m_document(source.toUtf8())
196 HtmlNode body = m_document.first("html").first("body");
198 m_text = tc.to_text(HtmlRange(body, body));
200 Text::pointer sentences = service->divide_into_sentences(scode, m_text->to_string());
201 for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence
203 Text::pointer words = service->divide_into_words(scode, s);
204 m_sentences.append(TextSentence::create(segment_id, words));
208 int TM::TextSegment::segment_id() const { return m_segment_id; }
211 * \brief 引数で指定されたインデックスの文について、CRC32を返します。
212 * \param service サービスへのポインタ。
214 * \param index 文のインデックス。
217 * このメンバは、不正なインデックスを指定した場合、0を返します。
219 quint32 TM::TextSegment::crc32(Service *service, int code, int index)
221 if(index < 0 || size() <= index) return 0;
223 TextSentence::pointer sentence = at(index);
224 quint32 result = sentence->crc32();
227 result = service->crc32(code, sentence->source_sentence()->to_string());
228 sentence->set_crc32(result);
233 int TM::TextSegment::size() const { return m_sentences.size(); }
235 TM::TextSentence::pointer TM::TextSegment::at(int index)
237 assert(0 <= index && index < m_sentences.size());
238 return m_sentences.at(index);
241 TM::TextSegment::iterator TM::TextSegment::begin() { return m_sentences.begin(); }
243 TM::TextSegment::iterator TM::TextSegment::end() { return m_sentences.end(); }
245 HtmlNode::pointer TM::TextSegment::find_html_node_by_offset(int offset)
247 HtmlNode::pointer result;
249 for(Text::pointer p = m_text->begin(); p; p = p->next())
251 UserData::pointer ud = p->data();
252 assert(ud->type() == HtmlData::Type);
253 HtmlData *hd = static_cast<HtmlData*>(ud.get());
254 if(hd->begin() <= offset && offset <= hd->tail())
256 result = hd->node().lself();
264 * \brief セグメント全体をHTML文字列に変換します。
266 QString TM::TextSegment::to_html()
269 for(TextSentence::pointer sentence : m_sentences)
271 if(!sentence->target_sentence())
272 result += to_html_from_source(sentence);
273 else result += to_html_from_target(sentence);
280 * \brief 原文をHTMLに変換します。
282 * 訳文のついていない文のために在ります。
284 QString TM::TextSegment::to_html_from_source(TextSentence::pointer sentence)
288 UserData *ud = sentence->source_sentence()->data().get();
289 assert(ud->type() == RangeData::Type);
290 RangeData *rd = static_cast<RangeData*>(ud);
291 int sentence_offset = rd->begin();
293 Text::pointer source_sentence = sentence->source_sentence();
294 if(!source_sentence) return "";
296 for(Text::pointer word = source_sentence->begin(); word; word = word->next())
298 QString string = word->to_string();
299 UserData *ud = word->data().get();
300 assert(ud->type() == RangeData::Type);
301 RangeData *rd = static_cast<RangeData*>(ud);
302 int word_offset = sentence_offset + rd->begin();
303 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
304 if(node) hc.append(node, string);
305 else hc.append(string);
307 return hc.to_string();
311 * \brief 訳文をHTMLに変換します。
313 QString TM::TextSegment::to_html_from_target(TextSentence::pointer sentence)
317 WordLinker* linker = sentence->linker();
318 UserData *ud = sentence->source_sentence()->data().get();
319 assert(ud->type() == RangeData::Type);
320 RangeData *rd = static_cast<RangeData*>(ud);
321 int sentence_offset = rd->begin();
323 Text::pointer target_sentence = sentence->target_sentence();
324 if(!target_sentence) return "";
326 for(Text::pointer word = target_sentence->begin(); word; word = word->next())
328 QString string = word->to_string();
329 WordLink::pointer link = linker->find(WordLink::Target, word);
330 if(!link) hc.append(string);
333 UserData::pointer ud = link->sources()->at(0)->data();
334 assert(ud->type() == RangeData::Type);
335 RangeData *rd = static_cast<RangeData*>(ud.get());
336 int word_offset = sentence_offset + rd->begin();
337 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
338 if(node) hc.append(node, string);
339 else hc.append(string);
342 return hc.to_string();
345 TM::TextSegment::pointer TM::TextSegment::create(
346 Service *service, int scode, int segment_id, QString source)
348 return pointer(new TextSegment(service, scode, segment_id, source));
351 // SocketConnection -----------------------------------------------------------
353 TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
354 EditorWidget *editor_widget, QWebSocket *socket)
356 , m_settings(settings)
358 , m_mutex(QMutex::Recursive)
359 , m_editor_widget(editor_widget)
362 , m_source_language_code(0)
363 , m_target_language_code(0)
365 connect(socket, SIGNAL(textMessageReceived(QString const&)),
366 this, SLOT(onTextMessageReceived(QString const&)));
367 connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
368 this, SLOT(onBinaryMessageReceived(QByteArray const&)));
371 TM::SocketConnection::~SocketConnection()
373 m_editor_widget->detach(this);
376 QWebSocket* TM::SocketConnection::socket()
378 QWebSocket * result = qobject_cast<QWebSocket*>(parent());
382 void TM::SocketConnection::send_message(QString const &message)
384 QWebSocket *ws = socket();
385 if(ws) ws->sendTextMessage(message);
388 void TM::SocketConnection::send_message(QJsonObject const &json)
392 send_message(doc.toJson().data());
396 * \brief データベースへセンテンスの登録とブラウザへの反映を行います。
397 * \param segment_id セグメントのID。
398 * \param index センテンスのインデックス。
400 void TM::SocketConnection::save_sentence(int segment_id, int index)
405 segment_map_iterator it = m_segments.find(segment_id);
406 assert(it != m_segments.end());
407 TextSegment::pointer segment = it.value();
410 TextSentence::pointer sentence = segment->at(index);
411 assert(sentence->source_sentence());
412 if(!sentence->target_sentence()) return;
415 int scode = m_editor_widget->source_language();
416 int tcode = m_editor_widget->target_language();
419 quint32 previous_crc = segment->crc32(m_service, scode, index - 1);
420 quint32 next_crc = segment->crc32(m_service, scode, index + 1);
423 m_service->insert_sentence(m_site_id, scode, tcode, sentence, previous_crc, next_crc);
426 set_segment(segment_id, segment->to_html());
429 void TM::SocketConnection::changeEditMode(bool edit_mode)
431 set_edit_mode(edit_mode);
435 * \brief ブラウザに編集モードを反映します。
437 * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
438 * 編集モードではリンクのクリックは無効です。
440 void TM::SocketConnection::set_edit_mode(bool edit_mode)
442 if(edit_mode == m_edit_mode) return;
444 m_edit_mode = edit_mode;
446 json["cmd"] = "set_edit_mode";
447 json["edit_mode"] = edit_mode;
452 * \brief ウェブブラウザへセグメントの訳文をセットします。
453 * \param segment_id セグメントのID。
454 * \param html セグメントに対応するHTML文字列。
456 void TM::SocketConnection::set_segment(int segment_id, QString html)
459 json["cmd"] = "set_segment";
460 json["segment_id"] = segment_id;
466 * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
469 void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
471 assert(json.contains("segment_id"));
472 int segment_id = json["segment_id"].toString().toInt();
474 segment_map_iterator it = m_segments.find(segment_id);
475 assert(it != m_segments.end());
476 TextSegment::pointer segment = it.value();
478 if(segment != m_current_segment)
480 m_current_segment = segment;
481 m_editor_widget->set_segment(m_current_segment);
486 * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
487 * focusコマンドが発行されたときに呼び出されます。
489 void TM::SocketConnection::do_focus(QJsonObject const &)
491 m_editor_widget->attach(this);
493 m_editor_widget->set_edit_mode(m_edit_mode);
494 if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
497 void TM::SocketConnection::do_blur(QJsonObject const &)
499 //m_editor_widget->detach(this);
502 void TM::SocketConnection::do_load(QJsonObject const &json)
504 m_editor_widget->attach(this);
506 set_edit_mode(m_editor_widget->edit_mode());
508 m_source_language_code = m_editor_widget->source_language();
509 m_target_language_code = m_editor_widget->target_language();
510 m_url = QUrl(json["url"].toString());
511 m_site_id = m_service->find_site_id(m_url.host());
515 * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
517 * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
518 * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
519 * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
521 void TM::SocketConnection::do_load_segment(QJsonObject const &json)
523 assert(json.contains("segment_id"));
524 int segment_id = json["segment_id"].toString().toInt();
525 assert(!m_segments.contains(segment_id));
526 assert(json.contains("html"));
527 QString html = json["html"].toString();
530 TextSegment::pointer segment = TextSegment::create(
531 m_service, m_source_language_code, segment_id, html);
532 m_segments.insert(segment_id, segment);
536 //m_service->find_sentence(m_site_id,)
540 //json["cmd"] = "set_edit_mode";
541 //json["edit_mode"] = edit_mode;
542 //send_message(json);
545 void TM::SocketConnection::onTextMessageReceived(QString const &message)
547 QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
548 QString cmd = json["cmd"].toString();
549 if(cmd == "edit_segment") do_edit_segment(json);
550 else if(cmd == "focus") do_focus(json);
551 else if(cmd == "blur") do_blur(json);
552 else if(cmd == "load") do_load(json);
553 else if(cmd == "load_segment") do_load_segment(json);
556 void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
561 // SocketServer ---------------------------------------------------------------
563 TM::SocketServer::SocketServer(Settings *settings, Service* service,
564 EditorWidget *editor_widget, QObject *parent)
566 , m_settings(settings)
568 , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
569 QWebSocketServer::NonSecureMode, this))
570 , m_editor_widget(editor_widget)
572 connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
574 quint16 port = m_settings->value("SocketServer/port").toUInt();
575 bool result = m_server->listen(QHostAddress::LocalHost, port);
576 if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
577 if(!result) qFatal("An error occured in SocketServer().");
580 TM::SocketServer::~SocketServer()
584 quint16 TM::SocketServer::port() const
586 return m_server->serverPort();
589 void TM::SocketServer::abort()
591 for(QWebSocket *ws : m_sockets)
599 void TM::SocketServer::onNewConnection()
601 QWebSocket *socket = m_server->nextPendingConnection();
602 connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
603 new SocketConnection(m_settings, m_service, m_editor_widget, socket);
604 m_sockets.push_back(socket);
607 void TM::SocketServer::onDisconnected()
609 QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
610 m_sockets.removeOne(socket);
611 socket->deleteLater();