В сообщениях блога за последние две недели мы делились введением в библиотеку Javascript под названием React, а затем показали, как делать полезные вещи с помощью компонентов React. На этой неделе мы собираемся использовать эти руководства, чтобы показать, как анализировать значения XML, возвращаемые MTurk, в том числе те, которые получены от Workers, предоставляющих ответы на HIT.

Для этого мы воспользуемся сторонней библиотекой Javascript под названием xml2js. Подробнее об этой библиотеке вы можете узнать здесь.

Установка библиотеки xml2js

В предыдущих сообщениях блога мы создали приложение React в каталоге / Users / johndoe / mturk-app. Давайте теперь перейдем в этот каталог и скажем диспетчеру пакетов узлов (NPM) установить xml2js:

cd /Users/johndoe/mturk-app
npm install xml2js

По завершении вы должны увидеть такое сообщение.

$ npm install xml2js
└─┬ [email protected]
└── [email protected]

Вот и все. Теперь давайте покажем, как использовать эту библиотеку для быстрой работы по синтаксическому анализу значений XML из MTurk.

Получение отправленных заданий от сотрудников

В предыдущих руководствах по Javascript и React мы показали, как вызывать GetAccountBalance и ListHITs. Эти операции возвращают значения, которые можно использовать сразу же без анализа. В этом руководстве мы назовем ListAssignmentsForHIT, который включает поле Ответ в форме XML. Это то, что мы здесь будем разбирать.

Если вы прошли вместе с предварительным просмотром двух руководств (Часть 1, Часть 2), просто добавьте строку, выделенную полужирным шрифтом ниже, в свой код:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import AWS from 'aws-sdk';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import xml2js from "xml2js";

Затем мы добавим новый метод (выделен полужирным шрифтом) в наш класс App.js:

...
getAssignmentsForHIT(hitId) {
  AWS.config.update({
    "accessKeyId": "YOUR_ACCESS_KEY_HERE",
    "secretAccessKey": "YOUR_SECRET_KEY_HERE",
    "region": "us-east-1"
  });
  
  const mTurkClient = new AWS.MTurk();
  mTurkClient.listAssignmentsForHIT({HITId: hitId}, (err, data) => {
    if (err) {
      console.warn("Error making the mTurk API call:", err);
    } else {
      // The call was a success
      const assignments = data.Assignments;
      this.setState({ assignmentsForCurrentHIT: assignments });
    }
  })
}
render() {
  var accountBalanceToDisplay = "loading...";
  ...

Вышеупомянутый метод вызовет MTurk для получения назначений при задании hitId. Он сохранит эти назначения в переменной состояния React, которая называется assignmentsForCurrentHIT. Это означает, что переменная всегда будет хранить назначения для последнего идентификатора HIT, который мы передали в getAssignmentsForHIT.

Затем давайте инициализируем эту переменную состояния (assignmentsForCurrentHIT) в нашем конструкторе. Для этого просто добавьте в конструктор строку, выделенную жирным шрифтом:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      mturkAccountBalance: null,
      mturkHITs: [],
      assignmentsForCurrentHIT: []
    }
  }

Замечательно. Мы реализовали метод получения заданий по HIT Id. Теперь давайте заставим его что-то отображать для пользователя. Для этого мы изменим наш метод render (), добавив строки, выделенные жирным шрифтом ниже:

render() {
  var accountBalanceToDisplay = "loading...";
  if (this.state.mturkAccountBalance != null) { 
    accountBalanceToDisplay = this.state.mturkAccountBalance
  } 

  const reactTableColumns = [
  ...

  var assignmentComponent = "";
  if (this.state.assignmentsForCurrentHIT.length > 0) {
    // If we've loaded the assignments for the first HIT,
    // then create a simple component to show its info
    const firstAssignment = this.state.assignmentsForCurrentHIT[0];

    assignmentComponent = (
      <div>
        <h3>Assignment {firstAssignment.AssignmentId} answer:</h3>
        <code>
          {firstAssignment.Answer}
        </code>
      </div>
    );
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1 className="App-title">Welcome to my MTurk app</h1>
      </header>
      <p className="App-intro">
        Your Requester account balance is {accountBalanceToDisplay}
        <br/>
        You have {this.state.mturkHITs.length} HIT(s).
      </p>
      <ReactTable 
        data={this.state.mturkHITs} 
        columns={reactTableColumns} 
        defaultPageSize={10} 
      />
      <hr/>
      {assignmentComponent}
    </div>
  );

Мы почти все собрали вместе, нам просто нужно вызвать getAssignmentsForHIT с HITId. Для этого мы добавим строку в метод getHIT, который уже вызывается в нашем классе App.js. Просто добавьте строки, выделенные полужирным шрифтом ниже, в свой метод getMTurkHITs:

getMTurkHITs() {
   AWS.config.update({
     "accessKeyId": "YOUR_ACCESS_KEY_HERE",
     "secretAccessKey": "YOUR_SECRET_KEY_HERE",
     "region": "us-east-1"
   });
  
   const mTurkClient = new AWS.MTurk();
   mTurkClient.listHITs((err, data) => {
   if (err) {
     console.warn("Error making the mTurk API call:", err);
   } else {
     // The call was a success
     const hits = data.HITs;
     this.setState({ mturkHITs: hits });
     // Let's also load the assignments for the first HIT
     this.getAssignmentsForHIT(hits[0].HITId);
    }
  })
}

Теперь вы должны быть уверены, что увидите результат при перезагрузке приложения. Если в первом HIT, возвращаемом listHITs, есть хотя бы одно присваивание, оно должно выглядеть примерно так:

Хорошая работа. Мы успешно получили список HIT из MTurk, а затем извлекли все назначения из первого HIT. Мы вывели AssignmentId и поле ответа.

Но ответ не особенно полезен для пользователя в том виде, в котором он показан здесь. Они должны прочитать XML, чтобы извлечь тот факт, что ответ на вопрос этого HIT - «Дональд Дж. Трамп», а источник - «Википедия». Мы можем добиться большего, если проанализируем XML и вернем только эти значения. Давайте сделаем это.

Разбор XML ответа с помощью xml2js

Чтобы превратить ответ XML во что-то более полезное, мы воспользуемся библиотекой xml2js для его анализа. Для этого вы добавите в свой класс App.js следующий код:

parseAnswerXML(answerXML, callbackFunction) {
  xml2js.parseString(answerXML, function (err, result) {
    let answer = result.QuestionFormAnswers;

    if (answer) {
      // We have an answer
      answer = answer.Answer;

      // ParsedAnswer
      var parsedAnswer = {};

      for (var i = 0; i < answer.length; i++) {
        parsedAnswer[answer[i]["QuestionIdentifier"]] = 
          answer[i]["FreeText"][0];
      }

      // We have a result. Return it via the callbackFunction.
      callbackFunction(parsedAnswer);
    } else {
      // No answer value yet.
    }
  });
}

Теперь давайте вызовем это в нашем методе render (), добавив строки, выделенные полужирным шрифтом ниже (и заменив строки assignmentComponent на строки, выделенные жирным шрифтом ниже):

const reactTableColumns = [
  ...

  var assignmentComponent = "";
  if (this.state.assignmentsForCurrentHIT.length > 0) {
    // If we've loaded the assignments for the first HIT,
    // then create a simple component to show its info
    const firstAssignment = this.state.assignmentsForCurrentHIT[0];
    var parsedAnswers = {};

    // Fetch the parsed XML
    this.parseAnswerXML(firstAssignment.Answer, 
      (answer) => { parsedAnswers = answer; })

    assignmentComponent = (
      <div>
        <h3>Assignment {firstAssignment.AssignmentId} answer:</h3>
        Answer: {parsedAnswers.answer}<br/>
        Source: {parsedAnswers.source}
      </div>
    );
  }

  return (
...

В только что добавленном коде мы вызываем parseAnswerXML. Мы впервые вызываем метод, в который передали функцию обратного вызова. Мы передали следующую функцию: (answer) = ›{parsedAnswers = answer; } который, по сути, динамически создает функцию, эквивалентную:

newFunction(answer) {
  parsedAnswers = answer;
}

и достаточно умен, чтобы иметь возможность отображать переменную parsedAnswers вне области видимости функции во внутреннюю область видимости, чтобы она могла выполнять присваивание. Затем эта функция делает наши ответы доступными в виде ассоциативного массива Javascript (также известного как Hash), чтобы мы могли выполнять такие вызовы, как «parsedAnswer.source» в приведенном выше коде, она обращается к части XML ответа с помощью QuestionIdentifier «источник». Это гораздо более удобный способ доступа к полям в XML-ответе.

С указанным выше изменением кода, когда мы перезагружаем наше приложение, мы должны увидеть что-то похожее на это:

Поздравляю! Это гораздо более простой и удобный ответ, чем то, что у нас было раньше. И мы сделали это с помощью простого анализатора XML, который можно использовать в любом поле XML (MTurk или другое).

Подведение итогов

В этом руководстве мы основывались на предыдущих введениях в React и Javascript, чтобы показать, как преобразовать поля XML в простые в использовании ассоциативные массивы Javascript. Мы сделали это, вызвав метод MTurk API ListAssignmentsForHIT. Вы можете узнать больше о MTurk API, обратившись к документации по MTurk API здесь.

Надеемся, вам понравился сегодняшний урок. Если у вас есть вопросы, задайте их на нашем форуме MTurk. Чтобы стать инициатором запроса, зарегистрируйтесь здесь. Хотите внести свой вклад в качестве клиента Worker? Начни здесь.