diff --git a/.gitignore b/.gitignore
index f6b286c..f0454c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,14 +14,14 @@ gen/
out/
# Gradle files
-.gradle/
-build/
+**/.gradle/
+**/build/
# Local configuration file (sdk path, etc)
-local.properties
+**/local.properties
# Proguard folder generated by Eclipse
-proguard/
+**/proguard/
# Log Files
*.log
@@ -30,11 +30,12 @@ proguard/
.navigation/
# Android Studio captures folder
-captures/
+**/captures/
# Intellij
*.iml
.idea/workspace.xml
+**/.idea/*
# Keystore files
*.jks
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..59c36f7
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,17 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.0'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..88469f7
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,25 @@
+## Project-wide Gradle settings.
+#
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+#Fri Jul 22 18:38:34 BST 2016
+systemProp.http.proxyPort=8080
+systemProp.http.proxyUser=csotelo
+systemProp.http.proxyPassword=User\#\#345\#\#
+systemProp.https.proxyPassword=User\#\#345\#\#
+systemProp.https.proxyHost=10.164.212.25
+systemProp.http.nonProxyHosts=svn.bancocetelem.local,localhost,127.0.0.1
+systemProp.http.proxyHost=10.164.212.25
+systemProp.https.proxyPort=8080
+systemProp.https.nonProxyHosts=svn.bancocetelem.local,localhost,127.0.0.1
+systemProp.https.proxyUser=csotelo
diff --git a/ohcrapi/build.gradle b/ohcrapi/build.gradle
new file mode 100644
index 0000000..738fc20
--- /dev/null
+++ b/ohcrapi/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.github.dcendents.android-maven'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ abortOnError false
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:22.0.0'
+ compile 'com.rmtheis:tess-two:6.0.2'
+}
diff --git a/ohcrapi/proguard-rules.pro b/ohcrapi/proguard-rules.pro
new file mode 100644
index 0000000..3ab60b8
--- /dev/null
+++ b/ohcrapi/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/priyankvex/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/ohcrapi/src/androidTest/java/com/csot/ohcrapi/ApplicationTest.java b/ohcrapi/src/androidTest/java/com/csot/ohcrapi/ApplicationTest.java
new file mode 100644
index 0000000..40b9357
--- /dev/null
+++ b/ohcrapi/src/androidTest/java/com/csot/ohcrapi/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.csot.ohcrapi;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapi.java b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapi.java
new file mode 100644
index 0000000..69e8d65
--- /dev/null
+++ b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapi.java
@@ -0,0 +1,103 @@
+package com.csot.ohcrapi;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.googlecode.tesseract.android.TessBaseAPI;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Random;
+
+/**
+ * Description Created on 19-07-2016.
+ *
+ * @author csotelo
+ * @version $Revision : 1 $
+ */
+public class OhCRapi {
+ final static String TAG = "OCR";
+ final static Random random = new Random();
+ final static String url = "https://api.ocr.space/parse/image"; // OCR API Endpoints
+ final static String url2 = "https://apifree2.ocr.space/parse/image"; // OCR API Endpoints
+ static OhCRapi mInstance;
+ static TessBaseAPI mLocalOcrEngine;
+ String trainedDataLanguage = "eng";
+ String apiKey;
+
+ private OhCRapi(String trainedDataLanguage, String apiKey) {
+ this.trainedDataLanguage = trainedDataLanguage;
+ this.apiKey = apiKey;
+ }
+
+ public static void initRemote(String trainedDataLanguage, String apiKey) {
+ mInstance = new OhCRapi(trainedDataLanguage, apiKey);
+ }
+
+ //ProgressNotifier
+ public static void initLocal(Context ctx, String tessDirectoryPath, String trainedDataLanguage, TessBaseAPI.ProgressNotifier progressNotifier) {
+ if (mLocalOcrEngine == null) {
+ mLocalOcrEngine = touchTesseract(ctx, tessDirectoryPath, trainedDataLanguage, progressNotifier);
+ }
+ }
+
+ public static String getApiKey() {
+ return mInstance.apiKey;
+ }
+
+ public static String getTrainedDataLanguage() {
+ return mInstance.trainedDataLanguage;
+ }
+
+ public static String getOcrUrl() {
+ int num = random.nextInt(2);
+ return (num == 1) ? url : url2;
+ }
+
+ @NonNull
+ static TessBaseAPI touchTesseract(@NonNull Context ctx, @NonNull String tessDirectoryPath, @NonNull String trainedDataLanguage, @Nullable TessBaseAPI.ProgressNotifier progressNotifier) {
+ File tessDir = new File(tessDirectoryPath + File.separator + "tessdata");
+ tessDir.mkdir();
+ File tessData = new File(tessDir.getAbsolutePath() + File.separator + trainedDataLanguage + ".traineddata");
+ if (!tessData.exists()) {
+ try {
+ AssetManager assetManager = ctx.getAssets();
+ InputStream in = assetManager.open("tessdata/" + trainedDataLanguage + ".traineddata");
+ //GZIPInputStream gin = new GZIPInputStream(in);
+ // Output stream with the location where we have to write the eng.traineddata file.
+ OutputStream out = new FileOutputStream(tessData);
+
+ // Transfer bytes from in to out
+ byte[] buf = new byte[1024];
+ int len;
+ //while ((lenf = gin.read(buff)) > 0) {
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ in.close();
+ //gin.close();
+ out.close();
+// mInstance.trainedDataLanguage = trainedDataLanguage;
+ Log.v(OhCRapi.TAG, "Copied " + tessData.getAbsolutePath());
+ } catch (IOException e) {
+ Log.e(OhCRapi.TAG, "Was unable to copy " + tessData.getAbsolutePath() + " : " + e.toString());
+ }
+ } else {
+ Log.d(OhCRapi.TAG, "TessData already present");
+ }
+ TessBaseAPI tess;
+ if (progressNotifier != null) {
+ tess = new TessBaseAPI(progressNotifier);
+ } else {
+ tess = new TessBaseAPI();
+ }
+ tess.init(tessDirectoryPath, trainedDataLanguage);
+ return tess;
+ }
+}
diff --git a/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiListener.java b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiListener.java
new file mode 100644
index 0000000..888ddb1
--- /dev/null
+++ b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiListener.java
@@ -0,0 +1,16 @@
+package com.csot.ohcrapi;
+
+/**
+ * Description Created on 19-07-2016.
+ *
+ * @author csotelo
+ * @version $Revision : 1 $
+ */
+public interface OhCRapiListener {
+
+ void onOhCRapiStarted();
+
+// void onOhCRapiFinished(Bitmap bitmap, String recognizedText);
+
+ void onOhCRapiFinished(String recognizedText);
+}
diff --git a/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiLocalTask.java b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiLocalTask.java
new file mode 100644
index 0000000..b7a7d37
--- /dev/null
+++ b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiLocalTask.java
@@ -0,0 +1,168 @@
+package com.csot.ohcrapi;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.media.ExifInterface;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import com.googlecode.tesseract.android.TessBaseAPI;
+
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * Description Created on 19-07-2016.
+ *
+ * @author csotelo
+ * @version $Revision : 1 $
+ */
+public class OhCRapiLocalTask extends AsyncTask {
+ protected final static String TAG = OhCRapiLocalTask.class.getName();
+ private OhCRapiListener mOhCRapiListener;
+ private String filePath;
+ private Bitmap mBitmap;
+ private String scannedText;
+ private TessBaseAPI baseApi = OhCRapi.mLocalOcrEngine;
+ private Collection rectangles;
+
+ public OhCRapiLocalTask(OhCRapiListener OhCRapiListener, String filePath) {
+ this.mOhCRapiListener = OhCRapiListener;
+ this.filePath = filePath;
+ }
+
+ public OhCRapiLocalTask(OhCRapiListener OhCRapiListener, Bitmap bitmap) {
+ this.mOhCRapiListener = OhCRapiListener;
+ this.mBitmap = bitmap;
+ }
+
+ public OhCRapiLocalTask(OhCRapiListener OhCRapiListener, Bitmap bitmap, Collection rectangles) {
+ this.mOhCRapiListener = OhCRapiListener;
+ this.mBitmap = bitmap;
+ this.rectangles = rectangles;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ processImage();
+ scannedText = scanImage();
+ return null;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mOhCRapiListener.onOhCRapiStarted();
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ mOhCRapiListener.onOhCRapiFinished(scannedText);
+ }
+
+ private void processImage() {
+ if (mBitmap == null) {
+ int imageOrientationCode = getImageOrientation();
+ Bitmap rawBitmap = getBitmapFromPath();
+ // Getting the bitmap in right orientation.
+ this.mBitmap = rotateBitmap(rawBitmap, imageOrientationCode);
+ }
+ }
+
+ private Bitmap getBitmapFromPath() {
+ BitmapFactory.Options bmOptions = new BitmapFactory.Options();
+ bmOptions.inSampleSize = 4;
+ return BitmapFactory.decodeFile(this.filePath, bmOptions);
+ }
+
+ private int getImageOrientation() {
+ ExifInterface exif = null;
+ try {
+ exif = new ExifInterface(this.filePath);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ assert exif != null;
+ return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.ORIENTATION_UNDEFINED);
+ }
+
+ private Bitmap rotateBitmap(Bitmap bitmap, int orientation) {
+
+ Matrix matrix = new Matrix();
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return bitmap;
+ case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
+ matrix.setScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ matrix.setRotate(180);
+ break;
+ case ExifInterface.ORIENTATION_FLIP_VERTICAL:
+ matrix.setRotate(180);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_TRANSPOSE:
+ matrix.setRotate(90);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ matrix.setRotate(90);
+ break;
+ case ExifInterface.ORIENTATION_TRANSVERSE:
+ matrix.setRotate(-90);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ matrix.setRotate(-90);
+ break;
+ default:
+ return bitmap;
+ }
+ try {
+ Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ bitmap.recycle();
+ return bmRotated;
+ } catch (OutOfMemoryError e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private String scanImage() {
+ long startTime = System.nanoTime();
+ String recognizedText;
+ baseApi.setImage(this.mBitmap);
+ if (rectangles == null || rectangles.isEmpty()) {
+ recognizedText = baseApi.getUTF8Text();
+ baseApi.clear();
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (Rect r : rectangles) {
+ baseApi.setRectangle(r);
+ String s = baseApi.getUTF8Text();
+ Log.d(TAG, s);
+ sb.append(s);
+ sb.append("\n");
+ }
+ recognizedText = sb.toString();
+ }
+ long endTime = System.nanoTime();
+
+ long duration = (endTime - startTime) / 1000000; //divide by 1000000 to get milliseconds.
+ Log.v(TAG, "Took: " + duration + "ms");
+ return recognizedText;
+ }
+
+}
+
+
+
+
+
+
diff --git a/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiRemoteTask.java b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiRemoteTask.java
new file mode 100644
index 0000000..55b4a91
--- /dev/null
+++ b/ohcrapi/src/main/java/com/csot/ohcrapi/OhCRapiRemoteTask.java
@@ -0,0 +1,224 @@
+package com.csot.ohcrapi;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * Description Created on 19-07-2016.
+ *
+ * @author csotelo
+ * @version $Revision : 1 $
+ */
+public class OhCRapiRemoteTask extends AsyncTask {
+ final static String TAG = OhCRapiRemoteTask.class.getName();
+ private String mApiKey;
+ private boolean isOverlayRequired = false;
+ private Object image;
+ private String mLanguage;
+ private OhCRapiListener OhCRapiListener;
+
+ public OhCRapiRemoteTask(Object image, boolean isOverlayRequired, OhCRapiListener listener) {
+ this.mApiKey = OhCRapi.getApiKey();
+ this.mLanguage = OhCRapi.getTrainedDataLanguage();
+ this.isOverlayRequired = isOverlayRequired;
+ this.image = image;
+ this.OhCRapiListener = listener;
+ }
+
+ public OhCRapiRemoteTask(Object image, OhCRapiListener listener) {
+ this.mApiKey = OhCRapi.getApiKey();
+ this.mLanguage = OhCRapi.getTrainedDataLanguage();
+ this.image = image;
+ this.OhCRapiListener = listener;
+ }
+
+ public static byte[] getByteArrayFromFile(File file) throws IOException {
+ byte[] buffer = new byte[(int) file.length()];
+ InputStream ios = null;
+ try {
+ ios = new FileInputStream(file);
+ if (ios.read(buffer) == -1) {
+ throw new IOException(
+ "EOF reached while trying to read the whole file");
+ }
+ } finally {
+ try {
+ if (ios != null) {
+ ios.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+ return buffer;
+ }
+
+ public String getPostDataString(JSONObject params) throws Exception {
+
+ StringBuilder result = new StringBuilder();
+ boolean first = true;
+
+ Iterator itr = params.keys();
+
+ while (itr.hasNext()) {
+
+ String key = itr.next();
+ Object value = params.get(key);
+
+ if (first) {
+ first = false;
+ } else {
+ result.append("&");
+ }
+
+ result.append(URLEncoder.encode(key, "UTF-8"));
+ result.append("=");
+ result.append(URLEncoder.encode(value.toString(), "UTF-8"));
+
+ }
+ return result.toString();
+ }
+
+ @Override
+ protected String doInBackground(Void... params) {
+ try {
+ return sendPost(mApiKey, isOverlayRequired, image, mLanguage);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to call service", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ OhCRapiListener.onOhCRapiStarted();
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ super.onPostExecute(result);
+ OhCRapiListener.onOhCRapiFinished(result);
+ }
+
+ private String sendPost(String apiKey, boolean isOverlayRequired, Object image, String language) throws Exception {
+ if (image instanceof Bitmap) {
+ return sendPost(apiKey, isOverlayRequired, (Bitmap) image, language);
+ } else if (image instanceof String) {
+ return sendPost(apiKey, isOverlayRequired, (String) image, language);
+ } else if (image instanceof File) {
+ return sendPost(apiKey, isOverlayRequired, (File) image, language);
+ } else {
+ return sendPost(apiKey, isOverlayRequired, (byte[]) image, language);
+ }
+ }
+
+ private String sendPost(String apiKey, boolean isOverlayRequired, String imageUrl, String language) throws Exception {
+
+ URL obj = new URL(OhCRapi.getOcrUrl()); // OCR API Endpoints
+ HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
+
+ //add request header
+ con.setRequestMethod("POST");
+ con.setRequestProperty("User-Agent", "Mozilla/5.0");
+ con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
+
+
+ JSONObject postDataParams = new JSONObject();
+
+ postDataParams.put("apikey", apiKey);
+ postDataParams.put("isOverlayRequired", isOverlayRequired);
+ postDataParams.put("url", imageUrl);
+ postDataParams.put("language", language);
+
+
+ // Send post request
+ con.setDoOutput(true);
+ DataOutputStream wr = new DataOutputStream(con.getOutputStream());
+ wr.writeBytes(getPostDataString(postDataParams));
+ wr.flush();
+ wr.close();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ String inputLine;
+ StringBuffer response = new StringBuffer();
+
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+
+ //return result
+ return String.valueOf(response);
+ }
+
+ private String sendPost(String apiKey, boolean isOverlayRequired, Bitmap imageFile, String language) throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(imageFile.getByteCount());
+ imageFile.copyPixelsToBuffer(byteBuffer);
+ return sendPost(apiKey, isOverlayRequired, byteBuffer.array(), language);
+ }
+
+ private String sendPost(String apiKey, boolean isOverlayRequired, File imageFile, String language) throws Exception {
+ return sendPost(apiKey, isOverlayRequired, getByteArrayFromFile(imageFile), language);
+ }
+
+ private String sendPost(String apiKey, boolean isOverlayRequired, byte[] data, String language) throws Exception {
+
+ URL obj = new URL(OhCRapi.getOcrUrl()); // OCR API Endpoints
+ HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
+
+ //add request header
+ con.setRequestMethod("POST");
+ con.setRequestProperty("User-Agent", "Mozilla/5.0");
+ con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
+
+
+ JSONObject postDataParams = new JSONObject();
+
+ postDataParams.put("apikey", apiKey);
+ postDataParams.put("isOverlayRequired", isOverlayRequired);
+// byte[] base64Encoded = Base64.encode(data, Base64.DEFAULT);
+ String dataString = new String(data);
+ postDataParams.put("file", dataString);
+ postDataParams.put("language", language);
+
+ String logMsg = String.format("URL: " + OhCRapi.getOcrUrl() + " > posting the following JSONObject - %s", postDataParams.toString());
+ Log.v(TAG, logMsg);
+// logMsg = logMsg.replaceAll("\"file\":\"[^\"]+\"", "\"file\":\"FILE CONTENT NOT INCLUDED IN LOGS\"");
+// Log.d(TAG, logMsg);
+ // Send post request
+ con.setDoOutput(true);
+ DataOutputStream wr = new DataOutputStream(con.getOutputStream());
+ wr.writeBytes(getPostDataString(postDataParams));
+ wr.flush();
+ wr.close();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ String inputLine;
+ StringBuffer response = new StringBuffer();
+
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+
+ //return result
+ return String.valueOf(response);
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e856d84
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':ohcrapi'