/**
 * Replace "^" prefix-edtags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param array $pa_values Array of values; keys must match tag names
 * @param array $pa_options Supported options are:
 *			prefix = string to add to beginning of tags extracted from template before doing lookup into value array
 *			removePrefix = string to remove from tags extracted from template before doing lookup into value array
 *			getFrom = a model instance to draw data from. If set, $pa_values is ignored.
 *
 * @return string Output of processed template
 */
function caProcessTemplate($ps_template, $pa_values, $pa_options = null)
{
    $vs_prefix = isset($pa_options['prefix']) ? $pa_options['prefix'] : null;
    $vs_remove_prefix = isset($pa_options['removePrefix']) ? $pa_options['removePrefix'] : null;
    $va_tags = caGetTemplateTags($ps_template);
    $t_instance = null;
    if (isset($pa_options['getFrom']) && method_exists($pa_options['getFrom'], 'get')) {
        $t_instance = $pa_options['getFrom'];
    }
    foreach ($va_tags as $vs_tag) {
        $va_tmp = explode("~", $vs_tag);
        $vs_proc_tag = array_shift($va_tmp);
        if ($vs_remove_prefix) {
            $vs_proc_tag = str_replace($vs_remove_prefix, '', $vs_proc_tag);
        }
        if ($vs_prefix) {
            $vs_proc_tag = $vs_prefix . $vs_proc_tag;
        }
        if ($t_instance && ($vs_gotten_val = $t_instance->get($vs_proc_tag, $pa_options))) {
            $vs_gotten_val = caProcessTemplateTagDirectives($vs_gotten_val, $va_tmp);
            $ps_template = str_replace('^' . $vs_tag, $vs_gotten_val, $ps_template);
        } else {
            if (is_array($vs_val = isset($pa_values[$vs_proc_tag]) ? $pa_values[$vs_proc_tag] : '')) {
                // If value is an array try to make a string of it
                $vs_val = join(" ", $vs_val);
            }
            $vs_val = caProcessTemplateTagDirectives($vs_val, $va_tmp);
            $ps_template = preg_replace("!\\^(?={$vs_tag}[^A-Za-z0-9]+|{$vs_tag}\$){$vs_tag}!", $vs_val, $ps_template);
        }
    }
    return $ps_template;
}
/**
 * Replace "^" prefix-edtags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param array $pa_values Array of values; keys must match tag names
 * @param array $pa_options Supported options are:
 *			prefix = string to add to beginning of tags extracted from template before doing lookup into value array
 *			removePrefix = string to remove from tags extracted from template before doing lookup into value array
 *			getFrom = a model instance to draw data from. If set, $pa_values is ignored.
 *			quote = quote replacement values (Eg. ^ca_objects.idno becomes "2015.001" rather than 2015.001). Value containing quotes will be escaped with a backslash. [Default is false]
 *
 * @return string Output of processed template
 */
function caProcessTemplate($ps_template, $pa_values, $pa_options = null)
{
    $ps_prefix = caGetOption('prefix', $pa_options, null);
    $ps_remove_prefix = caGetOption('removePrefix', $pa_options, null);
    $pb_quote = caGetOption('quote', $pa_options, false);
    $va_tags = caGetTemplateTags($ps_template);
    $t_instance = null;
    if (isset($pa_options['getFrom']) && method_exists($pa_options['getFrom'], 'get')) {
        $t_instance = $pa_options['getFrom'];
    }
    foreach ($va_tags as $vs_tag) {
        $va_tmp = explode("~", $vs_tag);
        $vs_proc_tag = array_shift($va_tmp);
        if ($ps_remove_prefix) {
            $vs_proc_tag = str_replace($ps_remove_prefix, '', $vs_proc_tag);
        }
        if ($ps_prefix && !preg_match("!^" . preg_quote($ps_prefix, "!") . "!", $vs_proc_tag)) {
            $vs_proc_tag = $ps_prefix . $vs_proc_tag;
        }
        if ($t_instance && ($vs_gotten_val = $t_instance->get($vs_proc_tag, $pa_options))) {
            $vs_gotten_val = caProcessTemplateTagDirectives($vs_gotten_val, $va_tmp);
            $ps_template = preg_replace("/\\^" . preg_quote($vs_tag, '/') . "(?![A-Za-z0-9]+)/", $vs_gotten_val, $ps_template);
        } else {
            if (is_array($vs_val = isset($pa_values[$vs_proc_tag]) ? $pa_values[$vs_proc_tag] : '')) {
                // If value is an array try to make a string of it
                $vs_val = join(" ", $vs_val);
            }
            $vs_val = caProcessTemplateTagDirectives($vs_val, $va_tmp);
            if ($pb_quote) {
                $vs_val = '"' . addslashes($vs_val) . '"';
            }
            $ps_template = preg_replace("!\\^(?={$vs_tag}[^A-Za-z0-9]+|{$vs_tag}\$){$vs_tag}!", str_replace("\$", "\\\$", $vs_val), $ps_template);
            // escape "$" to prevent interpretation as backreferences
        }
    }
    return $ps_template;
}
/**
 * Replace "^" tags (eg. ^forename) in a template with values from an array
 *
 * @param string $ps_template String with embedded tags. Tags are just alphanumeric strings prefixed with a caret ("^")
 * @param array $pa_values Array of values; keys must match tag names
 * @param array $pa_options Supported options are:
 *			prefix = string to add to beginning of tags extracted from template before doing lookup into value array
 *			removePrefix = string to remove from tags extracted from template before doing lookup into value array
 *			getFrom = a model instance to draw data from. If set, $pa_values is ignored.
 *
 * @return string Output of processed template
 */
function caProcessTemplate($ps_template, $pa_values, $pa_options = null)
{
    $vs_prefix = isset($pa_options['prefix']) ? $pa_options['prefix'] : null;
    $vs_remove_prefix = isset($pa_options['removePrefix']) ? $pa_options['removePrefix'] : null;
    $va_tags = array();
    if (preg_match_all(__CA_BUNDLE_DISPLAY_TEMPLATE_TAG_REGEX__, $ps_template, $va_matches)) {
        foreach ($va_matches[1] as $vn_i => $vs_possible_tag) {
            $va_matches[1][$vn_i] = rtrim($vs_possible_tag, "/.");
            // remove trailing slashes and periods
        }
        $va_tags = $va_matches[1];
    }
    $t_instance = null;
    if (isset($pa_options['getFrom']) && method_exists($pa_options['getFrom'], 'get')) {
        $t_instance = $pa_options['getFrom'];
    }
    foreach ($va_tags as $vs_tag) {
        $va_tmp = explode("~", $vs_tag);
        $vs_proc_tag = array_shift($va_tmp);
        if ($vs_remove_prefix) {
            $vs_proc_tag = str_replace($vs_remove_prefix, '', $vs_proc_tag);
        }
        if ($vs_prefix) {
            $vs_proc_tag = $vs_prefix . $vs_proc_tag;
        }
        if ($t_instance && ($vs_gotten_val = $t_instance->get($vs_proc_tag, $pa_options))) {
            $vs_gotten_val = caProcessTemplateTagDirectives($vs_gotten_val, $va_tmp);
            $ps_template = str_replace('^' . $vs_tag, $vs_gotten_val, $ps_template);
        } else {
            if (is_array($vs_val = isset($pa_values[$vs_proc_tag]) ? $pa_values[$vs_proc_tag] : '')) {
                // If value is an array try to make a string of it
                $vs_val = join(" ", $vs_val);
            }
            $vs_val = caProcessTemplateTagDirectives($vs_val, $va_tmp);
            $ps_template = str_replace('^' . $vs_tag, $vs_val, $ps_template);
        }
    }
    return $ps_template;
}
 /**
  * Process template expression, replacing "^" prefixed placeholders with data values
  *
  * @param string $ps_placeholder An expression with at least one placeholder. (Eg. "^1"). Can also be a text expression with embedded placeholders (Eg. "This is ^1 and this is ^2). The placeholders are valid specifiers for the data reader being used prefixed with a caret ("^"). For flat formats like Excel, they will look like ^1, ^2, etc. For XML formats they will be Xpath. Eg. ^/teiHeader/encodingDesc/projectDesc
  * @param array $pa_source_data An array of data to use in substitutions. Array is indexed by placeholder name *without* the leading caret.
  * @param array $pa_item The mapping item information array containing settings for the current mapping.
  * @param int $pn_index The index of the value to return. For non-repeating values this should be omitted or set to zero. For repeating values, this is a zero-based index indicating which value is returned. If a value for the specified index does not exist null will be returned. If the index is set to null then an array with all values is returned.
  * @param array $pa_options An array of options. Options include:
  *		reader = An instance of BaseDataReader. Will be used to pull values for placeholders that are not defined in $pa_source_data. This is useful for formats like XML where placeholders may be arbitrary XPath expressions that must be executed rather than parsed. [Default is null]
  *		returnAsString = Return array of repeating values as string using delimiter. Has effect only is $pn_index parameter is set to null. [Default is false]
  *		delimiter = Delimiter to join array values with when returnAsString option is set; or the delimiter to use when breaking apart a value for return via the returnDelimitedValueAt option. [Default is ";"]
  *		returnDelimitedValueAt = Return a specific part of a value delimited by the "delimiter" option. Only has effect when returning a specific index of a repeating value (Eg. $pn_index is not null). The option value is a zero-based index. [Default is null – return entire value]
  *
  * @return mixed An array or string
  */
 public static function parsePlaceholder($ps_placeholder, $pa_source_data, $pa_item, $pn_index = 0, $pa_options = null)
 {
     $o_reader = caGetOption('reader', $pa_options, null);
     $ps_placeholder = trim($ps_placeholder);
     $vs_key = substr($ps_placeholder, 1);
     if ($ps_placeholder[0] == '^' && strpos($ps_placeholder, '^', 1) === false) {
         // Placeholder is a single caret-value
         $va_tag = explode('~', $vs_key);
         if ($o_reader) {
             $vm_val = $o_reader->get($va_tag[0], array('returnAsArray' => true));
         } else {
             if (!isset($pa_source_data[$va_tag[0]])) {
                 return null;
             }
             $vm_val = $pa_source_data[$va_tag[0]];
         }
         if ($va_tag[1]) {
             foreach ($vm_val as $vn_i => $vs_val) {
                 $vm_val[$vn_i] = caProcessTemplateTagDirectives($vs_val, [$va_tag[1]]);
             }
         }
     } elseif (strpos($ps_placeholder, '^') !== false) {
         // Placeholder is a full template – requires extra processing
         if ($o_reader) {
             $va_tags = array();
             // get a list of all tags in placeholder
             if (preg_match_all(__CA_BUNDLE_DISPLAY_TEMPLATE_TAG_REGEX__, $ps_placeholder, $va_matches)) {
                 foreach ($va_matches[1] as $vn_i => $vs_possible_tag) {
                     $va_matches[1][$vn_i] = rtrim($vs_possible_tag, "/.");
                     // remove trailing slashes and periods
                 }
                 $va_tags = $va_matches[1];
             }
             // Make sure all tags are in source data array, otherwise try to pull them from the reader.
             // Some formats, mainly XML, can take expressions (XPath for XML) that are not precalculated in the array
             //print "p=$ps_placeholder\n";
             //print_R($va_tags);
             foreach ($va_tags as $vs_tag) {
                 $va_tag = explode('~', $vs_tag);
                 if (isset($pa_source_data[$va_tag[0]])) {
                     continue;
                 }
                 $va_val = $o_reader->get($va_tag[0], array('returnAsArray' => true));
                 $pa_source_data[$va_tag[0]] = $va_val[$pn_index];
             }
             $vm_val = caProcessTemplate($ps_placeholder, $pa_source_data);
         } else {
             // Is plain text
             if (!isset($pa_source_data[substr($ps_placeholder, 1)])) {
                 return null;
             }
             $vm_val = $pa_source_data[substr($ps_placeholder, 1)];
         }
     } else {
         $vm_val = $ps_placeholder;
     }
     // Get specific index for repeating value
     if (is_array($vm_val) && !is_null($pn_index)) {
         $vm_val = isset($vm_val[$pn_index]) ? $vm_val[$pn_index] : null;
     }
     // If we're returning the entire array, do processing on members and return
     if (is_array($vm_val)) {
         foreach ($vm_val as $vn_i => $vs_val) {
             if (is_array($pa_item['settings']['original_values']) && ($vn_ix = array_search(mb_strtolower($vs_val), $pa_item['settings']['original_values'])) !== false) {
                 $vs_val = $pa_item['settings']['replacement_values'][$vn_ix];
             }
             $vm_val[$vn_i] = trim($vs_val);
         }
         $vm_val = caProcessImportItemSettingsForValue($vm_val, $pa_item['settings']);
         if (caGetOption("returnAsString", $pa_options, false)) {
             $va_delimiter = caGetOption("delimiter", $pa_options, ';');
             if (is_array($va_delimiter)) {
                 $vs_delimiter = array_shift($va_delimiter);
             } else {
                 $vs_delimiter = $va_delimiter;
             }
             return join($vs_delimiter, $vm_val);
         }
         return $vm_val;
     }
     if (!is_null($pn_index) && !is_null($vs_get_at_index = caGetOption('returnDelimitedValueAt', $pa_options, null)) && ($va_delimiter = caGetOption("delimiter", $pa_options, ';'))) {
         if (!is_array($va_delimiter)) {
             $va_delimiter = array($va_delimiter);
         }
         foreach ($va_delimiter as $vn_index => $vs_delim) {
             if (!trim($vs_delim, "\t ")) {
                 unset($va_delimiter[$vn_index]);
                 continue;
             }
             $va_delimiter[$vn_index] = preg_quote($vs_delim, "!");
         }
         $va_val = preg_split("!(" . join("|", $va_delimiter) . ")!", $vm_val);
         $vm_val = isset($va_val[$vs_get_at_index]) ? $va_val[$vs_get_at_index] : null;
     }
     $vm_val = trim($vm_val);
     if (is_array($pa_item['settings']['original_values']) && ($vn_i = array_search(mb_strtolower($vm_val), $pa_item['settings']['original_values'])) !== false) {
         $vm_val = $pa_item['settings']['replacement_values'][$vn_i];
     }
     $vm_val = caProcessImportItemSettingsForValue($vm_val, $pa_item['settings']);
     return trim($vm_val);
 }