chunk-031-schema-helper.js

I’ll build this as a standalone advanced chunk-031-schema-helper.js helper for RX Theme, focused on safe JSON-LD schema creation, validation, insertion, updating, and medical/article/breadcrumb/FAQ schema support.

Below is a strong advanced-level file for:

rx-theme/assets/static-js/static-chunks-js/chunk-031-schema-helper.js

It creates a global helper:

window.RXThemeSchemaHelper

It can generate and inject JSON-LD schema for:

Article, MedicalWebPage, MedicalCondition, FAQPage, BreadcrumbList, WebSite, Organization, Person/Author, ImageObject, HowTo, Speakable, Review, AggregateRating, VideoObject, and generic custom schema.

/*!
 * RX Theme - Chunk 031 Schema Helper
 * File: assets/static-js/static-chunks-js/chunk-031-schema-helper.js
 * Version: 1.0.0
 *
 * Purpose:
 * Advanced JSON-LD schema helper for RX Theme.
 * Adds structured data safely, avoids duplicates, validates required fields,
 * supports medical article schema, FAQ schema, breadcrumb schema, website schema,
 * organization schema, author schema, image schema, video schema, and custom schema.
 */

(function RXThemeSchemaHelperFactory(window, document) {
  'use strict';

  if (!window || !document) {
    return;
  }

  var RX_SCHEMA_GLOBAL = 'RXThemeSchemaHelper';
  var RX_SCHEMA_VERSION = '1.0.0';
  var RX_SCHEMA_SCRIPT_PREFIX = 'rx-schema-jsonld-';
  var RX_SCHEMA_DEFAULT_CONTEXT = 'https://schema.org';

  if (window[RX_SCHEMA_GLOBAL] && window[RX_SCHEMA_GLOBAL].version) {
    return;
  }

  var RXSchemaUtils = {
    isObject: function isObject(value) {
      return Object.prototype.toString.call(value) === '[object Object]';
    },

    isArray: function isArray(value) {
      return Array.isArray(value);
    },

    isString: function isString(value) {
      return typeof value === 'string';
    },

    isNumber: function isNumber(value) {
      return typeof value === 'number' && !Number.isNaN(value);
    },

    isBoolean: function isBoolean(value) {
      return typeof value === 'boolean';
    },

    isFunction: function isFunction(value) {
      return typeof value === 'function';
    },

    isEmpty: function isEmpty(value) {
      if (value === null || value === undefined) {
        return true;
      }

      if (typeof value === 'string' && value.trim() === '') {
        return true;
      }

      if (Array.isArray(value) && value.length === 0) {
        return true;
      }

      if (this.isObject(value) && Object.keys(value).length === 0) {
        return true;
      }

      return false;
    },

    trim: function trim(value) {
      return typeof value === 'string' ? value.trim() : value;
    },

    removeHtml: function removeHtml(value) {
      if (!value || typeof value !== 'string') {
        return '';
      }

      var div = document.createElement('div');
      div.innerHTML = value;
      return (div.textContent || div.innerText || '').replace(/\s+/g, ' ').trim();
    },

    truncate: function truncate(value, maxLength) {
      if (!value || typeof value !== 'string') {
        return '';
      }

      var cleanValue = value.trim();
      var limit = maxLength || 300;

      if (cleanValue.length <= limit) {
        return cleanValue;
      }

      return cleanValue.substring(0, limit).replace(/\s+\S*$/, '') + '...';
    },

    normalizeUrl: function normalizeUrl(url) {
      if (!url || typeof url !== 'string') {
        return '';
      }

      var value = url.trim();

      if (!value) {
        return '';
      }

      try {
        return new URL(value, window.location.origin).href;
      } catch (error) {
        return value;
      }
    },

    isValidUrl: function isValidUrl(url) {
      if (!url || typeof url !== 'string') {
        return false;
      }

      try {
        new URL(url, window.location.origin);
        return true;
      } catch (error) {
        return false;
      }
    },

    normalizeDate: function normalizeDate(value) {
      if (!value) {
        return '';
      }

      if (value instanceof Date && !Number.isNaN(value.getTime())) {
        return value.toISOString();
      }

      if (typeof value === 'string') {
        var date = new Date(value);

        if (!Number.isNaN(date.getTime())) {
          return date.toISOString();
        }

        return value;
      }

      return '';
    },

    toArray: function toArray(value) {
      if (Array.isArray(value)) {
        return value;
      }

      if (value === null || value === undefined || value === '') {
        return [];
      }

      return [value];
    },

    uniqueArray: function uniqueArray(arr) {
      var output = [];
      var seen = {};

      this.toArray(arr).forEach(function eachItem(item) {
        var key = typeof item === 'string' ? item : JSON.stringify(item);

        if (!seen[key]) {
          seen[key] = true;
          output.push(item);
        }
      });

      return output;
    },

    deepMerge: function deepMerge(target) {
      var output = target && this.isObject(target) ? target : {};
      var self = this;

      for (var i = 1; i < arguments.length; i += 1) {
        var source = arguments[i];

        if (!self.isObject(source)) {
          continue;
        }

        Object.keys(source).forEach(function eachKey(key) {
          var sourceValue = source[key];

          if (self.isObject(sourceValue)) {
            if (!self.isObject(output[key])) {
              output[key] = {};
            }

            output[key] = self.deepMerge(output[key], sourceValue);
          } else if (Array.isArray(sourceValue)) {
            output[key] = sourceValue.slice();
          } else {
            output[key] = sourceValue;
          }
        });
      }

      return output;
    },

    cleanObject: function cleanObject(obj) {
      var self = this;

      if (Array.isArray(obj)) {
        return obj
          .map(function mapItem(item) {
            return self.cleanObject(item);
          })
          .filter(function filterItem(item) {
            return !self.isEmpty(item);
          });
      }

      if (!self.isObject(obj)) {
        return obj;
      }

      var cleaned = {};

      Object.keys(obj).forEach(function eachKey(key) {
        var value = obj[key];

        if (value === null || value === undefined || value === '') {
          return;
        }

        if (Array.isArray(value)) {
          var cleanArray = self.cleanObject(value);

          if (cleanArray.length > 0) {
            cleaned[key] = cleanArray;
          }

          return;
        }

        if (self.isObject(value)) {
          var cleanChild = self.cleanObject(value);

          if (Object.keys(cleanChild).length > 0) {
            cleaned[key] = cleanChild;
          }

          return;
        }

        cleaned[key] = value;
      });

      return cleaned;
    },

    slugify: function slugify(value) {
      if (!value || typeof value !== 'string') {
        return '';
      }

      return value
        .toLowerCase()
        .replace(/<[^>]*>/g, '')
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .substring(0, 80);
    },

    getCanonicalUrl: function getCanonicalUrl() {
      var canonical = document.querySelector('link[rel="canonical"]');

      if (canonical && canonical.href) {
        return canonical.href;
      }

      return window.location.href.split('#')[0];
    },

    getMetaContent: function getMetaContent(selectors) {
      var list = this.toArray(selectors);

      for (var i = 0; i < list.length; i += 1) {
        var element = document.querySelector(list[i]);

        if (element && element.getAttribute('content')) {
          return element.getAttribute('content').trim();
        }
      }

      return '';
    },

    getTitle: function getTitle() {
      var ogTitle = this.getMetaContent([
        'meta[property="og:title"]',
        'meta[name="twitter:title"]'
      ]);

      if (ogTitle) {
        return ogTitle;
      }

      if (document.title) {
        return document.title.trim();
      }

      var h1 = document.querySelector('h1');

      return h1 ? h1.textContent.trim() : '';
    },

    getDescription: function getDescription() {
      return this.getMetaContent([
        'meta[name="description"]',
        'meta[property="og:description"]',
        'meta[name="twitter:description"]'
      ]);
    },

    getImage: function getImage() {
      return this.getMetaContent([
        'meta[property="og:image"]',
        'meta[name="twitter:image"]'
      ]);
    },

    getLanguage: function getLanguage() {
      var html = document.documentElement;

      if (html && html.getAttribute('lang')) {
        return html.getAttribute('lang');
      }

      return 'en';
    },

    getSiteName: function getSiteName() {
      return this.getMetaContent([
        'meta[property="og:site_name"]',
        'meta[name="application-name"]'
      ]) || window.location.hostname.replace(/^www\./, '');
    },

    parseJson: function parseJson(value, fallback) {
      try {
        return JSON.parse(value);
      } catch (error) {
        return fallback || null;
      }
    },

    safeStringify: function safeStringify(value) {
      try {
        return JSON.stringify(value, null, 2);
      } catch (error) {
        return '';
      }
    },

    dispatchEvent: function dispatchEvent(name, detail) {
      try {
        window.dispatchEvent(
          new CustomEvent(name, {
            detail: detail || {}
          })
        );
      } catch (error) {
        var event = document.createEvent('CustomEvent');
        event.initCustomEvent(name, true, true, detail || {});
        window.dispatchEvent(event);
      }
    }
  };

  var RXSchemaDefaults = {
    organization: {
      name: 'RxHarun',
      url: window.location.origin,
      logo: '',
      sameAs: []
    },

    author: {
      name: 'RxHarun',
      url: window.location.origin,
      type: 'Person'
    },

    publisher: {
      name: 'RxHarun',
      logo: ''
    },

    medical: {
      medicalSpecialty: 'MedicalSpecialty',
      audience: 'Patient',
      reviewingAuthority: '',
      lastReviewed: ''
    }
  };

  var RXSchemaState = {
    inserted: {},
    registry: {},
    warnings: [],
    errors: [],
    config: {
      debug: false,
      autoClean: true,
      allowOverwrite: true,
      useGraph: false,
      idBase: RXSchemaUtils.getCanonicalUrl()
    }
  };

  function logDebug() {
    if (!RXSchemaState.config.debug || !window.console) {
      return;
    }

    window.console.log.apply(window.console, ['[RX Schema Helper]'].concat(Array.prototype.slice.call(arguments)));
  }

  function logWarn(message, data) {
    RXSchemaState.warnings.push({
      message: message,
      data: data || null,
      time: new Date().toISOString()
    });

    if (RXSchemaState.config.debug && window.console) {
      window.console.warn('[RX Schema Helper]', message, data || '');
    }
  }

  function logError(message, data) {
    RXSchemaState.errors.push({
      message: message,
      data: data || null,
      time: new Date().toISOString()
    });

    if (window.console) {
      window.console.error('[RX Schema Helper]', message, data || '');
    }
  }

  function createId(type, key) {
    var cleanType = RXSchemaUtils.slugify(type || 'schema');
    var cleanKey = RXSchemaUtils.slugify(key || RXSchemaUtils.getCanonicalUrl());

    return RX_SCHEMA_SCRIPT_PREFIX + cleanType + '-' + cleanKey;
  }

  function createSchemaId(fragment) {
    var base = RXSchemaState.config.idBase || RXSchemaUtils.getCanonicalUrl();
    return base.replace(/#.*$/, '') + '#' + RXSchemaUtils.slugify(fragment || 'schema');
  }

  function ensureContext(schema) {
    if (!RXSchemaUtils.isObject(schema)) {
      return schema;
    }

    if (!schema['@context']) {
      schema['@context'] = RX_SCHEMA_DEFAULT_CONTEXT;
    }

    return schema;
  }

  function ensureType(schema, type) {
    if (!RXSchemaUtils.isObject(schema)) {
      return schema;
    }

    if (!schema['@type'] && type) {
      schema['@type'] = type;
    }

    return schema;
  }

  function normalizePerson(person) {
    if (!person) {
      return null;
    }

    if (RXSchemaUtils.isString(person)) {
      return {
        '@type': 'Person',
        name: person
      };
    }

    if (!RXSchemaUtils.isObject(person)) {
      return null;
    }

    return RXSchemaUtils.cleanObject({
      '@type': person.type || person['@type'] || 'Person',
      '@id': person.id || person['@id'] || '',
      name: person.name || '',
      url: RXSchemaUtils.normalizeUrl(person.url || ''),
      image: person.image || '',
      jobTitle: person.jobTitle || '',
      description: person.description || '',
      sameAs: RXSchemaUtils.toArray(person.sameAs),
      affiliation: person.affiliation || '',
      worksFor: person.worksFor || ''
    });
  }

  function normalizeOrganization(org) {
    if (!org) {
      return null;
    }

    if (RXSchemaUtils.isString(org)) {
      return {
        '@type': 'Organization',
        name: org
      };
    }

    if (!RXSchemaUtils.isObject(org)) {
      return null;
    }

    var logo = org.logo || org.image || '';

    var output = {
      '@type': org.type || org['@type'] || 'Organization',
      '@id': org.id || org['@id'] || createSchemaId('organization'),
      name: org.name || RXSchemaDefaults.organization.name,
      url: RXSchemaUtils.normalizeUrl(org.url || RXSchemaDefaults.organization.url),
      logo: logo ? createImageObject(logo, org.name || RXSchemaDefaults.organization.name + ' Logo') : '',
      image: org.image || '',
      description: org.description || '',
      sameAs: RXSchemaUtils.uniqueArray(org.sameAs || RXSchemaDefaults.organization.sameAs),
      contactPoint: org.contactPoint || '',
      address: org.address || '',
      email: org.email || '',
      telephone: org.telephone || ''
    };

    return RXSchemaUtils.cleanObject(output);
  }

  function createImageObject(image, caption, width, height) {
    if (!image) {
      return null;
    }

    if (RXSchemaUtils.isObject(image)) {
      return RXSchemaUtils.cleanObject({
        '@type': 'ImageObject',
        '@id': image.id || image['@id'] || '',
        url: RXSchemaUtils.normalizeUrl(image.url || image.src || ''),
        contentUrl: RXSchemaUtils.normalizeUrl(image.contentUrl || image.url || image.src || ''),
        caption: image.caption || caption || '',
        width: image.width || width || '',
        height: image.height || height || '',
        representativeOfPage: image.representativeOfPage || ''
      });
    }

    if (RXSchemaUtils.isString(image)) {
      return RXSchemaUtils.cleanObject({
        '@type': 'ImageObject',
        url: RXSchemaUtils.normalizeUrl(image),
        contentUrl: RXSchemaUtils.normalizeUrl(image),
        caption: caption || '',
        width: width || '',
        height: height || ''
      });
    }

    return null;
  }

  function createRatingObject(rating) {
    if (!rating) {
      return null;
    }

    if (!RXSchemaUtils.isObject(rating)) {
      return null;
    }

    return RXSchemaUtils.cleanObject({
      '@type': 'AggregateRating',
      ratingValue: rating.ratingValue || rating.value || '',
      bestRating: rating.bestRating || 5,
      worstRating: rating.worstRating || 1,
      ratingCount: rating.ratingCount || rating.count || '',
      reviewCount: rating.reviewCount || ''
    });
  }

  function createReviewObject(review) {
    if (!review) {
      return null;
    }

    if (!RXSchemaUtils.isObject(review)) {
      return null;
    }

    return RXSchemaUtils.cleanObject({
      '@type': 'Review',
      author: normalizePerson(review.author || ''),
      datePublished: RXSchemaUtils.normalizeDate(review.datePublished || review.date || ''),
      reviewBody: RXSchemaUtils.removeHtml(review.reviewBody || review.body || ''),
      name: review.name || '',
      reviewRating: review.reviewRating
        ? {
            '@type': 'Rating',
            ratingValue: review.reviewRating.ratingValue || review.reviewRating.value || '',
            bestRating: review.reviewRating.bestRating || 5,
            worstRating: review.reviewRating.worstRating || 1
          }
        : ''
    });
  }

  function validateSchema(schema) {
    var result = {
      valid: true,
      warnings: [],
      errors: []
    };

    if (!RXSchemaUtils.isObject(schema)) {
      result.valid = false;
      result.errors.push('Schema must be an object.');
      return result;
    }

    if (!schema['@context']) {
      result.warnings.push('Missing @context. It will be added automatically.');
    }

    if (!schema['@type'] && !schema['@graph']) {
      result.valid = false;
      result.errors.push('Missing @type or @graph.');
    }

    if (schema.url && !RXSchemaUtils.isValidUrl(schema.url)) {
      result.warnings.push('Schema URL may be invalid.');
    }

    if (schema.image && RXSchemaUtils.isString(schema.image) && !RXSchemaUtils.isValidUrl(schema.image)) {
      result.warnings.push('Schema image URL may be invalid.');
    }

    return result;
  }

  function injectSchema(schema, options) {
    var config = RXSchemaUtils.deepMerge(
      {
        id: '',
        key: '',
        type: '',
        overwrite: RXSchemaState.config.allowOverwrite,
        clean: RXSchemaState.config.autoClean
      },
      options || {}
    );

    if (!RXSchemaUtils.isObject(schema)) {
      logError('Cannot inject schema. Schema is not an object.', schema);
      return false;
    }

    var finalSchema = RXSchemaUtils.deepMerge({}, schema);

    ensureContext(finalSchema);

    if (config.clean) {
      finalSchema = RXSchemaUtils.cleanObject(finalSchema);
    }

    var validation = validateSchema(finalSchema);

    if (!validation.valid) {
      logError('Schema validation failed.', validation.errors);
      return false;
    }

    validation.warnings.forEach(function eachWarning(warning) {
      logWarn(warning, finalSchema);
    });

    var scriptId = config.id || createId(config.type || finalSchema['@type'] || 'schema', config.key || finalSchema.name || finalSchema.url || location.pathname);
    var oldScript = document.getElementById(scriptId);

    if (oldScript && !config.overwrite) {
      logWarn('Schema already exists and overwrite is disabled.', scriptId);
      return false;
    }

    if (oldScript && oldScript.parentNode) {
      oldScript.parentNode.removeChild(oldScript);
    }

    var script = document.createElement('script');
    script.type = 'application/ld+json';
    script.id = scriptId;
    script.setAttribute('data-rx-schema', 'true');
    script.setAttribute('data-rx-schema-version', RX_SCHEMA_VERSION);

    var json = RXSchemaUtils.safeStringify(finalSchema);

    if (!json) {
      logError('Could not stringify schema.', finalSchema);
      return false;
    }

    script.text = json;
    document.head.appendChild(script);

    RXSchemaState.inserted[scriptId] = {
      id: scriptId,
      type: finalSchema['@type'] || 'Graph',
      schema: finalSchema,
      insertedAt: new Date().toISOString()
    };

    RXSchemaUtils.dispatchEvent('rx:schema:inserted', {
      id: scriptId,
      schema: finalSchema
    });

    logDebug('Schema inserted:', scriptId, finalSchema);

    return scriptId;
  }

  function removeSchema(id) {
    if (!id) {
      return false;
    }

    var script = document.getElementById(id);

    if (!script) {
      return false;
    }

    script.parentNode.removeChild(script);
    delete RXSchemaState.inserted[id];

    RXSchemaUtils.dispatchEvent('rx:schema:removed', {
      id: id
    });

    return true;
  }

  function clearSchemas() {
    var scripts = document.querySelectorAll('script[data-rx-schema="true"]');

    scripts.forEach(function eachScript(script) {
      if (script && script.parentNode) {
        script.parentNode.removeChild(script);
      }
    });

    RXSchemaState.inserted = {};

    RXSchemaUtils.dispatchEvent('rx:schema:cleared', {});
  }

  function createWebSiteSchema(data) {
    var input = data || {};
    var siteName = input.name || RXSchemaUtils.getSiteName();
    var siteUrl = RXSchemaUtils.normalizeUrl(input.url || window.location.origin);

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'WebSite',
      '@id': input.id || createSchemaId('website'),
      name: siteName,
      alternateName: input.alternateName || '',
      url: siteUrl,
      description: input.description || RXSchemaUtils.getDescription(),
      inLanguage: input.inLanguage || RXSchemaUtils.getLanguage(),
      publisher: normalizeOrganization(input.publisher || RXSchemaDefaults.organization),
      potentialAction: input.searchUrl
        ? {
            '@type': 'SearchAction',
            target: {
              '@type': 'EntryPoint',
              urlTemplate: input.searchUrl
            },
            'query-input': input.queryInput || 'required name=search_term_string'
          }
        : ''
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createOrganizationSchema(data) {
    var schema = normalizeOrganization(data || RXSchemaDefaults.organization);
    ensureContext(schema);
    return RXSchemaUtils.cleanObject(schema);
  }

  function createPersonSchema(data) {
    var schema = normalizePerson(data || RXSchemaDefaults.author);
    ensureContext(schema);
    return RXSchemaUtils.cleanObject(schema);
  }

  function createArticleSchema(data) {
    var input = data || {};
    var title = input.headline || input.title || RXSchemaUtils.getTitle();
    var description = input.description || RXSchemaUtils.getDescription();
    var canonical = RXSchemaUtils.normalizeUrl(input.url || RXSchemaUtils.getCanonicalUrl());
    var image = input.image || RXSchemaUtils.getImage();

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': input.type || input.articleType || 'Article',
      '@id': input.id || createSchemaId('article'),
      mainEntityOfPage: {
        '@type': 'WebPage',
        '@id': canonical
      },
      headline: RXSchemaUtils.truncate(title, 110),
      alternativeHeadline: input.alternativeHeadline || '',
      description: RXSchemaUtils.truncate(RXSchemaUtils.removeHtml(description), 500),
      image: image ? createImageObject(image, title, input.imageWidth, input.imageHeight) : '',
      url: canonical,
      datePublished: RXSchemaUtils.normalizeDate(input.datePublished || input.publishedTime || ''),
      dateModified: RXSchemaUtils.normalizeDate(input.dateModified || input.modifiedTime || input.datePublished || ''),
      author: normalizePerson(input.author || RXSchemaDefaults.author),
      publisher: normalizeOrganization(input.publisher || RXSchemaDefaults.organization),
      articleSection: input.articleSection || input.section || '',
      articleBody: input.articleBody ? RXSchemaUtils.removeHtml(input.articleBody) : '',
      wordCount: input.wordCount || '',
      keywords: RXSchemaUtils.toArray(input.keywords),
      inLanguage: input.inLanguage || RXSchemaUtils.getLanguage(),
      isAccessibleForFree: input.isAccessibleForFree !== undefined ? input.isAccessibleForFree : true,
      copyrightHolder: normalizeOrganization(input.copyrightHolder || input.publisher || RXSchemaDefaults.organization),
      copyrightYear: input.copyrightYear || new Date().getFullYear(),
      speakable: input.speakable || '',
      about: input.about || '',
      mentions: input.mentions || '',
      reviewedBy: input.reviewedBy ? normalizePerson(input.reviewedBy) : '',
      lastReviewed: RXSchemaUtils.normalizeDate(input.lastReviewed || '')
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createMedicalWebPageSchema(data) {
    var input = data || {};
    var canonical = RXSchemaUtils.normalizeUrl(input.url || RXSchemaUtils.getCanonicalUrl());
    var title = input.name || input.headline || RXSchemaUtils.getTitle();
    var description = input.description || RXSchemaUtils.getDescription();

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'MedicalWebPage',
      '@id': input.id || createSchemaId('medical-web-page'),
      name: RXSchemaUtils.truncate(title, 110),
      headline: RXSchemaUtils.truncate(title, 110),
      description: RXSchemaUtils.truncate(RXSchemaUtils.removeHtml(description), 500),
      url: canonical,
      mainEntityOfPage: canonical,
      inLanguage: input.inLanguage || RXSchemaUtils.getLanguage(),
      datePublished: RXSchemaUtils.normalizeDate(input.datePublished || ''),
      dateModified: RXSchemaUtils.normalizeDate(input.dateModified || input.datePublished || ''),
      lastReviewed: RXSchemaUtils.normalizeDate(input.lastReviewed || ''),
      reviewedBy: input.reviewedBy ? normalizePerson(input.reviewedBy) : '',
      author: normalizePerson(input.author || RXSchemaDefaults.author),
      publisher: normalizeOrganization(input.publisher || RXSchemaDefaults.organization),
      medicalAudience: input.medicalAudience || {
        '@type': 'MedicalAudience',
        audienceType: input.audienceType || 'Patient'
      },
      specialty: input.specialty || input.medicalSpecialty || '',
      about: input.about || '',
      mainEntity: input.mainEntity || '',
      primaryImageOfPage: input.image ? createImageObject(input.image, title) : '',
      breadcrumb: input.breadcrumb || ''
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createMedicalConditionSchema(data) {
    var input = data || {};
    var name = input.name || input.conditionName || RXSchemaUtils.getTitle();

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'MedicalCondition',
      '@id': input.id || createSchemaId('medical-condition-' + name),
      name: name,
      alternateName: RXSchemaUtils.toArray(input.alternateName),
      description: RXSchemaUtils.truncate(RXSchemaUtils.removeHtml(input.description || RXSchemaUtils.getDescription()), 1000),
      code: input.code || '',
      guideline: input.guideline || '',
      associatedAnatomy: input.associatedAnatomy || '',
      possibleTreatment: input.possibleTreatment || '',
      possibleComplication: input.possibleComplication || '',
      signOrSymptom: input.signOrSymptom || input.symptoms || '',
      riskFactor: input.riskFactor || '',
      cause: input.cause || input.causes || '',
      differentialDiagnosis: input.differentialDiagnosis || '',
      epidemiology: input.epidemiology || '',
      expectedPrognosis: input.expectedPrognosis || '',
      naturalProgression: input.naturalProgression || '',
      pathophysiology: input.pathophysiology || '',
      primaryPrevention: input.primaryPrevention || '',
      secondaryPrevention: input.secondaryPrevention || '',
      typicalTest: input.typicalTest || input.diagnosticTests || '',
      drug: input.drug || input.drugs || '',
      recognizingAuthority: input.recognizingAuthority || '',
      relevantSpecialty: input.relevantSpecialty || input.specialty || '',
      study: input.study || '',
      url: RXSchemaUtils.normalizeUrl(input.url || RXSchemaUtils.getCanonicalUrl())
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createFAQSchema(faqs) {
    var list = RXSchemaUtils.toArray(faqs);

    var mainEntity = list
      .map(function mapFAQ(item) {
        if (!item) {
          return null;
        }

        var question = '';
        var answer = '';

        if (RXSchemaUtils.isString(item)) {
          return null;
        }

        if (RXSchemaUtils.isObject(item)) {
          question = item.question || item.q || item.name || '';
          answer = item.answer || item.a || item.text || item.acceptedAnswer || '';
        }

        question = RXSchemaUtils.removeHtml(question);
        answer = RXSchemaUtils.removeHtml(answer);

        if (!question || !answer) {
          return null;
        }

        return {
          '@type': 'Question',
          name: question,
          acceptedAnswer: {
            '@type': 'Answer',
            text: answer
          }
        };
      })
      .filter(Boolean);

    return RXSchemaUtils.cleanObject({
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'FAQPage',
      '@id': createSchemaId('faq'),
      mainEntity: mainEntity
    });
  }

  function createBreadcrumbSchema(items) {
    var list = RXSchemaUtils.toArray(items);

    if (!list.length) {
      list = extractBreadcrumbsFromDOM();
    }

    var itemListElement = list
      .map(function mapCrumb(item, index) {
        if (!item) {
          return null;
        }

        if (RXSchemaUtils.isString(item)) {
          return {
            '@type': 'ListItem',
            position: index + 1,
            name: RXSchemaUtils.removeHtml(item)
          };
        }

        if (RXSchemaUtils.isObject(item)) {
          return RXSchemaUtils.cleanObject({
            '@type': 'ListItem',
            position: item.position || index + 1,
            name: RXSchemaUtils.removeHtml(item.name || item.title || ''),
            item: item.url || item.item ? RXSchemaUtils.normalizeUrl(item.url || item.item) : ''
          });
        }

        return null;
      })
      .filter(Boolean);

    return RXSchemaUtils.cleanObject({
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'BreadcrumbList',
      '@id': createSchemaId('breadcrumb'),
      itemListElement: itemListElement
    });
  }

  function extractBreadcrumbsFromDOM() {
    var selectors = [
      '.breadcrumb a',
      '.breadcrumbs a',
      '.rank-math-breadcrumb a',
      '.yoast-breadcrumb a',
      'nav[aria-label="breadcrumb"] a'
    ];

    var links = [];

    selectors.some(function eachSelector(selector) {
      var found = document.querySelectorAll(selector);

      if (found && found.length) {
        found.forEach(function eachLink(link) {
          links.push({
            name: link.textContent.trim(),
            url: link.href
          });
        });

        return true;
      }

      return false;
    });

    if (!links.length) {
      links.push({
        name: 'Home',
        url: window.location.origin
      });

      links.push({
        name: RXSchemaUtils.getTitle(),
        url: RXSchemaUtils.getCanonicalUrl()
      });
    }

    return links;
  }

  function createHowToSchema(data) {
    var input = data || {};
    var steps = RXSchemaUtils.toArray(input.steps).map(function mapStep(step, index) {
      if (RXSchemaUtils.isString(step)) {
        return {
          '@type': 'HowToStep',
          position: index + 1,
          name: 'Step ' + (index + 1),
          text: RXSchemaUtils.removeHtml(step)
        };
      }

      if (RXSchemaUtils.isObject(step)) {
        return RXSchemaUtils.cleanObject({
          '@type': 'HowToStep',
          position: step.position || index + 1,
          name: step.name || step.title || 'Step ' + (index + 1),
          text: RXSchemaUtils.removeHtml(step.text || step.description || ''),
          url: step.url || '',
          image: step.image ? createImageObject(step.image, step.name || '') : ''
        });
      }

      return null;
    }).filter(Boolean);

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'HowTo',
      '@id': input.id || createSchemaId('how-to'),
      name: input.name || RXSchemaUtils.getTitle(),
      description: RXSchemaUtils.removeHtml(input.description || RXSchemaUtils.getDescription()),
      image: input.image ? createImageObject(input.image, input.name || RXSchemaUtils.getTitle()) : '',
      totalTime: input.totalTime || '',
      estimatedCost: input.estimatedCost || '',
      supply: input.supply || '',
      tool: input.tool || '',
      step: steps
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createVideoSchema(data) {
    var input = data || {};

    var schema = {
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'VideoObject',
      '@id': input.id || createSchemaId('video-' + (input.name || RXSchemaUtils.getTitle())),
      name: input.name || RXSchemaUtils.getTitle(),
      description: RXSchemaUtils.removeHtml(input.description || RXSchemaUtils.getDescription()),
      thumbnailUrl: RXSchemaUtils.toArray(input.thumbnailUrl || input.thumbnail || RXSchemaUtils.getImage()).map(function mapThumb(url) {
        return RXSchemaUtils.normalizeUrl(url);
      }),
      uploadDate: RXSchemaUtils.normalizeDate(input.uploadDate || input.datePublished || ''),
      duration: input.duration || '',
      contentUrl: RXSchemaUtils.normalizeUrl(input.contentUrl || ''),
      embedUrl: RXSchemaUtils.normalizeUrl(input.embedUrl || ''),
      publisher: normalizeOrganization(input.publisher || RXSchemaDefaults.organization),
      author: normalizePerson(input.author || RXSchemaDefaults.author),
      transcript: input.transcript || ''
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createSpeakableSchema(data) {
    var input = data || {};

    var schema = {
      '@type': 'SpeakableSpecification',
      cssSelector: RXSchemaUtils.toArray(input.cssSelector || ['h1', '.entry-content p']),
      xpath: RXSchemaUtils.toArray(input.xpath)
    };

    return RXSchemaUtils.cleanObject(schema);
  }

  function createSearchActionSchema(searchUrl) {
    if (!searchUrl) {
      searchUrl = window.location.origin + '/?s={search_term_string}';
    }

    return {
      '@type': 'SearchAction',
      target: {
        '@type': 'EntryPoint',
        urlTemplate: searchUrl
      },
      'query-input': 'required name=search_term_string'
    };
  }

  function createGraphSchema(items) {
    var graph = RXSchemaUtils.toArray(items)
      .filter(function filterItem(item) {
        return RXSchemaUtils.isObject(item);
      })
      .map(function mapItem(item) {
        var clean = RXSchemaUtils.deepMerge({}, item);
        delete clean['@context'];
        return RXSchemaUtils.cleanObject(clean);
      });

    return RXSchemaUtils.cleanObject({
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@graph': graph
    });
  }

  function extractFAQFromDOM(options) {
    var config = RXSchemaUtils.deepMerge(
      {
        itemSelector: '.rx-faq-item, .faq-item, details',
        questionSelector: '.rx-faq-question, .faq-question, summary, h2, h3',
        answerSelector: '.rx-faq-answer, .faq-answer, p, div',
        maxItems: 20
      },
      options || {}
    );

    var items = document.querySelectorAll(config.itemSelector);
    var faqs = [];

    items.forEach(function eachFAQ(item) {
      if (faqs.length >= config.maxItems) {
        return;
      }

      var questionElement = item.querySelector(config.questionSelector);
      var answerElement = item.querySelector(config.answerSelector);

      if (!questionElement || !answerElement) {
        return;
      }

      var question = RXSchemaUtils.removeHtml(questionElement.textContent || '');
      var answer = RXSchemaUtils.removeHtml(answerElement.textContent || '');

      if (question && answer && question !== answer) {
        faqs.push({
          question: question,
          answer: answer
        });
      }
    });

    return faqs;
  }

  function detectPageType() {
    var body = document.body;

    if (!body) {
      return 'webpage';
    }

    var classes = body.className || '';

    if (/\bsingle-post\b/.test(classes)) {
      return 'article';
    }

    if (/\bpage\b/.test(classes)) {
      return 'webpage';
    }

    if (/\barchive\b/.test(classes)) {
      return 'collection';
    }

    if (/\bsearch\b/.test(classes)) {
      return 'search';
    }

    if (/\bhome\b|\bfront-page\b/.test(classes)) {
      return 'home';
    }

    return 'webpage';
  }

  function autoBuildBasicSchemas(options) {
    var config = RXSchemaUtils.deepMerge(
      {
        website: true,
        organization: true,
        breadcrumb: true,
        article: true,
        faq: true,
        medical: false,
        graph: true,
        data: {}
      },
      options || {}
    );

    var schemas = [];
    var pageType = detectPageType();

    if (config.website) {
      schemas.push(createWebSiteSchema(config.data.website || {}));
    }

    if (config.organization) {
      schemas.push(createOrganizationSchema(config.data.organization || RXSchemaDefaults.organization));
    }

    if (config.breadcrumb) {
      schemas.push(createBreadcrumbSchema(config.data.breadcrumb || []));
    }

    if (config.article && pageType === 'article') {
      schemas.push(createArticleSchema(config.data.article || {}));
    }

    if (config.medical) {
      schemas.push(createMedicalWebPageSchema(config.data.medical || {}));
    }

    if (config.faq) {
      var faqs = config.data.faq || extractFAQFromDOM();

      if (faqs.length) {
        schemas.push(createFAQSchema(faqs));
      }
    }

    if (config.graph) {
      return injectSchema(createGraphSchema(schemas), {
        type: 'graph',
        key: 'auto-basic'
      });
    }

    schemas.forEach(function eachSchema(schema) {
      injectSchema(schema, {
        type: schema['@type'],
        key: schema['@id'] || schema.name
      });
    });

    return schemas;
  }

  function readExistingJsonLd() {
    var scripts = document.querySelectorAll('script[type="application/ld+json"]');
    var schemas = [];

    scripts.forEach(function eachScript(script) {
      var json = RXSchemaUtils.parseJson(script.textContent || script.innerText || '', null);

      if (json) {
        schemas.push({
          id: script.id || '',
          schema: json
        });
      }
    });

    return schemas;
  }

  function findSchemaByType(type) {
    if (!type) {
      return [];
    }

    var existing = readExistingJsonLd();

    return existing.filter(function filterSchema(item) {
      var schema = item.schema;

      if (schema['@type'] === type) {
        return true;
      }

      if (Array.isArray(schema['@graph'])) {
        return schema['@graph'].some(function someGraph(node) {
          return node['@type'] === type;
        });
      }

      return false;
    });
  }

  function updateConfig(config) {
    if (!RXSchemaUtils.isObject(config)) {
      return RXSchemaState.config;
    }

    RXSchemaState.config = RXSchemaUtils.deepMerge(RXSchemaState.config, config);
    return RXSchemaState.config;
  }

  function setDefaults(defaults) {
    if (!RXSchemaUtils.isObject(defaults)) {
      return RXSchemaDefaults;
    }

    RXSchemaUtils.deepMerge(RXSchemaDefaults, defaults);
    return RXSchemaDefaults;
  }

  function registerSchemaBuilder(name, builder) {
    if (!name || !RXSchemaUtils.isFunction(builder)) {
      return false;
    }

    RXSchemaState.registry[name] = builder;
    return true;
  }

  function buildRegisteredSchema(name, data) {
    if (!name || !RXSchemaState.registry[name]) {
      logWarn('Registered schema builder not found.', name);
      return null;
    }

    try {
      return RXSchemaState.registry[name](data || {}, RXThemeSchemaHelper);
    } catch (error) {
      logError('Registered schema builder failed.', {
        name: name,
        error: error.message
      });

      return null;
    }
  }

  function injectRegisteredSchema(name, data, options) {
    var schema = buildRegisteredSchema(name, data);

    if (!schema) {
      return false;
    }

    return injectSchema(schema, RXSchemaUtils.deepMerge(
      {
        type: name,
        key: data && data.key ? data.key : name
      },
      options || {}
    ));
  }

  function getStatus() {
    return {
      version: RX_SCHEMA_VERSION,
      inserted: RXSchemaState.inserted,
      warnings: RXSchemaState.warnings,
      errors: RXSchemaState.errors,
      config: RXSchemaState.config,
      existingJsonLdCount: readExistingJsonLd().length
    };
  }

  var RXThemeSchemaHelper = {
    version: RX_SCHEMA_VERSION,

    utils: RXSchemaUtils,

    defaults: RXSchemaDefaults,

    state: RXSchemaState,

    config: updateConfig,

    setDefaults: setDefaults,

    createId: createId,

    createSchemaId: createSchemaId,

    ensureContext: ensureContext,

    ensureType: ensureType,

    normalizePerson: normalizePerson,

    normalizeOrganization: normalizeOrganization,

    createImageObject: createImageObject,

    createRatingObject: createRatingObject,

    createReviewObject: createReviewObject,

    createWebSiteSchema: createWebSiteSchema,

    createOrganizationSchema: createOrganizationSchema,

    createPersonSchema: createPersonSchema,

    createArticleSchema: createArticleSchema,

    createMedicalWebPageSchema: createMedicalWebPageSchema,

    createMedicalConditionSchema: createMedicalConditionSchema,

    createFAQSchema: createFAQSchema,

    createBreadcrumbSchema: createBreadcrumbSchema,

    createHowToSchema: createHowToSchema,

    createVideoSchema: createVideoSchema,

    createSpeakableSchema: createSpeakableSchema,

    createSearchActionSchema: createSearchActionSchema,

    createGraphSchema: createGraphSchema,

    extractFAQFromDOM: extractFAQFromDOM,

    extractBreadcrumbsFromDOM: extractBreadcrumbsFromDOM,

    detectPageType: detectPageType,

    validateSchema: validateSchema,

    inject: injectSchema,

    remove: removeSchema,

    clear: clearSchemas,

    autoBuild: autoBuildBasicSchemas,

    readExisting: readExistingJsonLd,

    findByType: findSchemaByType,

    register: registerSchemaBuilder,

    buildRegistered: buildRegisteredSchema,

    injectRegistered: injectRegisteredSchema,

    status: getStatus
  };

  registerSchemaBuilder('MedicalArticle', function MedicalArticleBuilder(data) {
    var article = createArticleSchema(
      RXSchemaUtils.deepMerge(
        {
          type: 'MedicalScholarlyArticle'
        },
        data || {}
      )
    );

    article.about = data && data.condition ? createMedicalConditionSchema(data.condition) : article.about;
    article.reviewedBy = data && data.reviewedBy ? normalizePerson(data.reviewedBy) : article.reviewedBy;
    article.lastReviewed = data && data.lastReviewed ? RXSchemaUtils.normalizeDate(data.lastReviewed) : article.lastReviewed;
    article.medicalAudience = data && data.medicalAudience
      ? data.medicalAudience
      : {
          '@type': 'MedicalAudience',
          audienceType: 'Patient'
        };

    return RXSchemaUtils.cleanObject(article);
  });

  registerSchemaBuilder('DoctorProfile', function DoctorProfileBuilder(data) {
    var input = data || {};

    return RXSchemaUtils.cleanObject({
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'Physician',
      '@id': input.id || createSchemaId('doctor-profile-' + (input.name || 'doctor')),
      name: input.name || '',
      description: input.description || '',
      image: input.image ? createImageObject(input.image, input.name || '') : '',
      url: RXSchemaUtils.normalizeUrl(input.url || RXSchemaUtils.getCanonicalUrl()),
      medicalSpecialty: input.medicalSpecialty || input.specialty || '',
      hospitalAffiliation: input.hospitalAffiliation || '',
      alumniOf: input.alumniOf || '',
      knowsAbout: RXSchemaUtils.toArray(input.knowsAbout),
      sameAs: RXSchemaUtils.toArray(input.sameAs),
      address: input.address || '',
      telephone: input.telephone || '',
      email: input.email || '',
      availableService: input.availableService || '',
      openingHoursSpecification: input.openingHoursSpecification || ''
    });
  });

  registerSchemaBuilder('MedicalClinic', function MedicalClinicBuilder(data) {
    var input = data || {};

    return RXSchemaUtils.cleanObject({
      '@context': RX_SCHEMA_DEFAULT_CONTEXT,
      '@type': 'MedicalClinic',
      '@id': input.id || createSchemaId('medical-clinic-' + (input.name || 'clinic')),
      name: input.name || '',
      description: input.description || '',
      url: RXSchemaUtils.normalizeUrl(input.url || RXSchemaUtils.getCanonicalUrl()),
      image: input.image ? createImageObject(input.image, input.name || '') : '',
      logo: input.logo ? createImageObject(input.logo, input.name || '') : '',
      medicalSpecialty: input.medicalSpecialty || '',
      address: input.address || '',
      geo: input.geo || '',
      telephone: input.telephone || '',
      email: input.email || '',
      priceRange: input.priceRange || '',
      openingHoursSpecification: input.openingHoursSpecification || '',
      sameAs: RXSchemaUtils.toArray(input.sameAs),
      aggregateRating: createRatingObject(input.aggregateRating || ''),
      review: RXSchemaUtils.toArray(input.review).map(createReviewObject).filter(Boolean)
    });
  });

  window[RX_SCHEMA_GLOBAL] = RXThemeSchemaHelper;

  RXSchemaUtils.dispatchEvent('rx:schema:ready', {
    helper: RXThemeSchemaHelper,
    version: RX_SCHEMA_VERSION
  });

  logDebug('Ready.');

})(window, document);

Example use in another JS file:

window.RXThemeSchemaHelper.inject(
  window.RXThemeSchemaHelper.createArticleSchema({
    headline: 'High Blood Pressure: Causes, Symptoms, Diagnosis and Treatment',
    description: 'A simple medical article about high blood pressure.',
    datePublished: '2026-05-18',
    dateModified: '2026-05-18',
    author: {
      name: 'RxHarun',
      url: 'https://rxharun.com'
    },
    publisher: {
      name: 'RxHarun',
      url: 'https://rxharun.com',
      logo: 'https://rxharun.com/logo.png'
    }
  }),
  {
    type: 'Article',
    key: 'high-blood-pressure'
  }
);

Best enqueue/defer idea for this helper:

wp_enqueue_script(
    'rx-schema-helper',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-031-schema-helper.js',
    array(),
    RX_THEME_VERSION,
    true
);

wp_script_add_data( 'rx-schema-helper', 'defer', true );

Leave a Reply

Your email address will not be published. Required fields are marked *