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());
202 int previous_crc = 0;
203 for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence
205 Text::pointer words = service->divide_into_words(scode, s);
206 int crc = service->crc32(scode, words->to_string());
207 TextSentence::pointer sentence =
208 TextSentence::create(segment_id, i++, crc, words);
209 sentence->set_previous_crc(previous_crc);
210 m_sentences.append(sentence);
215 for(int i = m_sentences.size() - 1; 0 <= i; i--)
217 TextSentence::pointer sentence = m_sentences.at(i);
218 sentence->set_next_crc(next_crc);
219 next_crc = sentence->crc();
223 int TM::TextSegment::segment_id() const { return m_segment_id; }
225 int TM::TextSegment::size() const { return m_sentences.size(); }
227 TM::TextSentence::pointer TM::TextSegment::at(int index)
229 assert(0 <= index && index < m_sentences.size());
230 return m_sentences.at(index);
233 TM::TextSegment::iterator TM::TextSegment::begin() { return m_sentences.begin(); }
235 TM::TextSegment::iterator TM::TextSegment::end() { return m_sentences.end(); }
237 HtmlNode::pointer TM::TextSegment::find_html_node_by_offset(int offset)
239 HtmlNode::pointer result;
241 for(Text::pointer p = m_text->begin(); p; p = p->next())
243 UserData::pointer ud = p->data();
244 assert(ud->type() == HtmlData::Type);
245 HtmlData *hd = static_cast<HtmlData*>(ud.get());
246 if(hd->begin() <= offset && offset <= hd->tail())
248 result = hd->node().lself();
256 * \brief セグメント全体をHTML文字列に変換します。
258 QString TM::TextSegment::to_html()
261 for(TextSentence::pointer sentence : m_sentences)
263 if(!sentence->target_sentence())
264 result += to_html_from_source(sentence);
265 else result += to_html_from_target(sentence);
272 * \brief 原文をHTMLに変換します。
274 * 訳文のついていない文のために在ります。
276 QString TM::TextSegment::to_html_from_source(TextSentence::pointer sentence)
280 UserData *ud = sentence->source_sentence()->data().get();
281 assert(ud->type() == RangeData::Type);
282 RangeData *rd = static_cast<RangeData*>(ud);
283 int sentence_offset = rd->begin();
285 Text::pointer source_sentence = sentence->source_sentence();
286 if(!source_sentence) return "";
288 for(Text::pointer word = source_sentence->begin(); word; word = word->next())
290 QString string = word->to_string();
291 UserData *ud = word->data().get();
292 assert(ud->type() == RangeData::Type);
293 RangeData *rd = static_cast<RangeData*>(ud);
294 int word_offset = sentence_offset + rd->begin();
295 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
296 if(node) hc.append(node, string);
297 else hc.append(string);
299 return hc.to_string();
303 * \brief 訳文をHTMLに変換します。
305 QString TM::TextSegment::to_html_from_target(TextSentence::pointer sentence)
309 WordLinker* linker = sentence->linker();
310 UserData *ud = sentence->source_sentence()->data().get();
311 assert(ud->type() == RangeData::Type);
312 RangeData *rd = static_cast<RangeData*>(ud);
313 int sentence_offset = rd->begin();
315 Text::pointer target_sentence = sentence->target_sentence();
316 if(!target_sentence) return "";
318 for(Text::pointer word = target_sentence->begin(); word; word = word->next())
320 QString string = word->to_string();
321 WordLink::pointer link = linker->find(WordLink::Target, word);
322 if(!link) hc.append(string);
325 UserData::pointer ud = link->sources()->at(0)->data();
326 assert(ud->type() == RangeData::Type);
327 RangeData *rd = static_cast<RangeData*>(ud.get());
328 int word_offset = sentence_offset + rd->begin();
329 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
330 if(node) hc.append(node, string);
331 else hc.append(string);
334 return hc.to_string();
337 TM::TextSegment::pointer TM::TextSegment::create(
338 Service *service, int scode, int segment_id, QString source)
340 return pointer(new TextSegment(service, scode, segment_id, source));
343 // SocketConnection -----------------------------------------------------------
345 TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
346 EditorWidget *editor_widget, QWebSocket *socket)
348 , m_settings(settings)
350 , m_mutex(QMutex::Recursive)
351 , m_editor_widget(editor_widget)
354 , m_source_language_code(0)
355 , m_target_language_code(0)
357 qRegisterMetaType<pointer>();
359 connect(socket, SIGNAL(textMessageReceived(QString const&)),
360 this, SLOT(onTextMessageReceived(QString const&)));
361 connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
362 this, SLOT(onBinaryMessageReceived(QByteArray const&)));
365 TM::SocketConnection::~SocketConnection()
367 m_editor_widget->detach(this);
370 QWebSocket* TM::SocketConnection::socket()
372 QWebSocket * result = qobject_cast<QWebSocket*>(parent());
376 void TM::SocketConnection::send_message(QString const &message)
378 QWebSocket *ws = socket();
379 if(ws) ws->sendTextMessage(message);
382 void TM::SocketConnection::send_message(QJsonObject const &json)
386 send_message(doc.toJson().data());
390 * \brief データベースへセンテンスの登録とブラウザへの反映を行います。
391 * \param segment_id セグメントのID。
392 * \param index センテンスのインデックス。
394 void TM::SocketConnection::save_sentence(int segment_id, int index)
399 segment_map_iterator it = m_segments.find(segment_id);
400 assert(it != m_segments.end());
401 TextSegment::pointer segment = it.value();
404 TextSentence::pointer sentence = segment->at(index);
405 assert(sentence->source_sentence());
406 if(!sentence->target_sentence()) return;
409 int scode = m_editor_widget->source_language();
410 int tcode = m_editor_widget->target_language();
413 m_service->insert_sentence(m_site_id, scode, tcode, sentence);
416 set_segment(segment_id, segment->to_html());
419 void TM::SocketConnection::sentence_found(
420 int segment_id, int index, sentence_data_type::pointer result)
424 segment_map_iterator it = m_segments.find(segment_id);
425 assert(it != m_segments.end());
426 TextSegment::pointer segment = it.value();
428 Text::pointer sentences = m_service->divide_into_sentences(
429 m_target_language_code, result->sentence);
430 Text::pointer words = m_service->divide_into_words(
431 m_target_language_code, sentences->begin());
432 segment->at(index)->set_target_sentence(words);
434 segment->at(index)->set_json(result->json);
436 set_segment(segment_id, segment->to_html());
439 void TM::SocketConnection::changeEditMode(bool edit_mode)
441 set_edit_mode(edit_mode);
445 * \brief ブラウザに編集モードを反映します。
447 * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
448 * 編集モードではリンクのクリックは無効です。
450 void TM::SocketConnection::set_edit_mode(bool edit_mode)
452 if(edit_mode == m_edit_mode) return;
454 m_edit_mode = edit_mode;
456 json["cmd"] = "set_edit_mode";
457 json["edit_mode"] = edit_mode;
462 * \brief ウェブブラウザへセグメントの訳文をセットします。
463 * \param segment_id セグメントのID。
464 * \param html セグメントに対応するHTML文字列。
466 void TM::SocketConnection::set_segment(int segment_id, QString html)
469 json["cmd"] = "set_segment";
470 json["segment_id"] = segment_id;
476 * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
479 void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
481 assert(json.contains("segment_id"));
482 int segment_id = json["segment_id"].toString().toInt();
484 segment_map_iterator it = m_segments.find(segment_id);
485 assert(it != m_segments.end());
486 TextSegment::pointer segment = it.value();
488 if(segment != m_current_segment)
490 m_current_segment = segment;
491 m_editor_widget->set_segment(m_current_segment);
496 * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
497 * focusコマンドが発行されたときに呼び出されます。
499 void TM::SocketConnection::do_focus(QJsonObject const &)
501 m_editor_widget->attach(this);
503 m_editor_widget->set_edit_mode(m_edit_mode);
504 if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
507 void TM::SocketConnection::do_blur(QJsonObject const &)
509 //m_editor_widget->detach(this);
512 void TM::SocketConnection::do_load(QJsonObject const &json)
514 m_editor_widget->attach(this);
516 set_edit_mode(m_editor_widget->edit_mode());
518 m_source_language_code = m_editor_widget->source_language();
519 m_target_language_code = m_editor_widget->target_language();
520 m_url = QUrl(json["url"].toString());
521 m_site_id = m_service->find_site_id(m_url.host());
525 * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
527 * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
528 * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
529 * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
531 void TM::SocketConnection::do_load_segment(QJsonObject const &json)
533 assert(json.contains("segment_id"));
534 int segment_id = json["segment_id"].toString().toInt();
535 assert(!m_segments.contains(segment_id));
536 assert(json.contains("html"));
537 QString html = json["html"].toString();
540 TextSegment::pointer segment = TextSegment::create(
541 m_service, m_source_language_code, segment_id, html);
542 m_segments.insert(segment_id, segment);
545 for(TextSentence::pointer sentence : *segment)
547 m_service->find_sentence(
548 m_site_id, m_source_language_code, m_target_language_code,
549 segment_id, sentence->index(),
550 sentence, pointer(this));
552 //m_service->find_sentence(m_site_id,)
556 //json["cmd"] = "set_edit_mode";
557 //json["edit_mode"] = edit_mode;
558 //send_message(json);
561 void TM::SocketConnection::onTextMessageReceived(QString const &message)
563 QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
564 QString cmd = json["cmd"].toString();
565 if(cmd == "edit_segment") do_edit_segment(json);
566 else if(cmd == "focus") do_focus(json);
567 else if(cmd == "blur") do_blur(json);
568 else if(cmd == "load") do_load(json);
569 else if(cmd == "load_segment") do_load_segment(json);
572 void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
577 // SocketServer ---------------------------------------------------------------
579 TM::SocketServer::SocketServer(Settings *settings, Service* service,
580 EditorWidget *editor_widget, QObject *parent)
582 , m_settings(settings)
584 , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
585 QWebSocketServer::NonSecureMode, this))
586 , m_editor_widget(editor_widget)
588 connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
590 quint16 port = m_settings->value("SocketServer/port").toUInt();
591 bool result = m_server->listen(QHostAddress::LocalHost, port);
592 if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
593 if(!result) qFatal("An error occured in SocketServer().");
596 TM::SocketServer::~SocketServer()
600 quint16 TM::SocketServer::port() const
602 return m_server->serverPort();
605 void TM::SocketServer::abort()
607 for(QWebSocket *ws : m_sockets)
615 void TM::SocketServer::onNewConnection()
617 QWebSocket *socket = m_server->nextPendingConnection();
618 connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
619 new SocketConnection(m_settings, m_service, m_editor_widget, socket);
620 m_sockets.push_back(socket);
623 void TM::SocketServer::onDisconnected()
625 QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
626 m_sockets.removeOne(socket);
627 socket->deleteLater();